给定一个最初为空的数组,然后进行 \(n\) 次操作,每次操作给定 \(x,l,r,k\),在数组末尾插入 \(x\) ,然后查询满足 \(mex(\{a_i, a_{i + 1}, \dots, a_j\}) = k\) 的数对 \((i,j)\) 的个数,强制在线。
\(mex(S)\) 表示集合 \(S\) 中最小的未出现的 自然数。
\(n \leq 2 \times 10^5\)。
为表示方便,下面记 \(mex(i, j) = mex(\{a_i, a_{i + 1}, \dots, a_j\})\)。
容易发现,如果向集合 \(S\) 中添加数,\(mex(S)\) 不会减小。
所以如果固定右端点 \(j\),\(mex(i, j)\) 是非严格单调减的;固定左端点 \(i\),\(mex(i, j)\) 是非严格单调增的。
维护一个 \(n \times n\) 矩阵,\(A[i][j] = mex(j, i)\),则矩阵 \(A\) 的每一行从左到右都是非严格单调减的,每一列从上到下都是非严格单调增的。
第 \(i\) 次操作增加一个数 \(x\),考虑从第 \(i - 1\) 行拓展到第 \(i\) 行时矩阵 \(A\) 的变化。
首先 \(A[i][i]=[x=0]\)。
下面只考虑 \(j < i\) 的情况。
如果 \(A[i-1][j]=x\),则 \(A[i][j]>x\),否则 \(A[i][j] = A[i - 1][j]\)。
所以每次变化的只有等于 \(x\) 的那些位置,根据单调性,这是一段区间,记为 \([p,q]\)。
设 \(j \in [p, q]\),要求 \(mex(j, i)\),只需要知道最后一次出现的位置小于 \(j\) 的最小的数。所以用线段树维护序列 \(seq_{lastpos[x]} = x\) ,每次求区间最小值,找到对应位置 \(index\),这个位置右边的所有 \(A[i][j]\) 都等于这个最小值,然后令 \(q = index\),反复迭代,直到待求区间为空。
分析一下这个操作的复杂度,每次相当于删除一个区间 \([p,q]\),添加若干个小区间。
开始只有一个区间,最后的区间数不超过 \(n\),中间只会删除最多 \(n\) 次区间。
所以添加区间的总次数不会超过 \(2n\) ,复杂度为 \(O(n \log n)\)。
然后考虑回答询问。
矩形交的面积
我们用四元组 \((top,bottom,left,right)\) 描述一个矩形 \(\{(i,j):top \leq i \leq bottom,left \leq j \leq right\}\)。
每次询问可以看作是询问矩形 \((l, r, l, r)\) 内 \(A[i][j]=k\) 的个数。
那么由上面 \(mex\) 的单调性可以发现,\(A[i][j] = k\) 的点可以组成一个个连通块,每个连通块由若干个矩形构成阶梯状(矩形的 \(left\) 相同,\(right\) 递增)。 且前一个连通块的 \(right\) 小于后一个连通块的 \(left\)。
问题转化为求这些矩形与询问矩形交的面积之和。
用 std::vector
存下每个 \(k\)
的所有矩形,维护前缀面积和,二分出相交与非相交的临界点,对于完全包含的用前缀和搞定,对于相交却不包含的,由于是阶梯状,所以也可以
\(O(1)\) 计算,细节有点多。
由上面的复杂度分析可知,所有矩形的数量不会超过 \(2n\) 个,所以总复杂度 \(O(n \log n)\)。
#include <bits/stdc++.h>
#ifdef LOCAL
#define dbg(args...) std::cerr << "\033[32;1m" << #args << " -> ", err(args)
#else
#define dbg(...)
#endif
inline void err() { std::cerr << "\033[0m\n"; }
template<class T, class... U>
inline void err(const T &x, const U &... a) { std::cerr << x << ' '; err(a...); }
template <class T>
inline void readInt(T &w) {
char c, p = 0;
while (!isdigit(c = getchar())) p = c == '-';
for (w = c & 15; isdigit(c = getchar());) w = w * 10 + (c & 15);
if (p) w = -w;
}
template <class T, class... U>
inline void readInt(T &w, U &... a) { readInt(w), readInt(a...); }
template <class T, class U>
inline bool smin(T &x, const U &y) { return y < x ? x = y, 1 : 0; }
template <class T, class U>
inline bool smax(T &x, const U &y) { return x < y ? x = y, 1 : 0; }
typedef long long LL;
typedef std::pair<int, int> PII;
constexpr int N(2e5 + 5);
int n;
struct Rect {
int t, b, l, r; // top, bottem, left, right (closed intervals)
(int t, int b, int l, int r): t(t), b(b), l(l), r(r) {}
Rectinline Rect operator&(const Rect &rhs) const {
return Rect(std::max(t, rhs.t), std::min(b, rhs.b), std::max(l, rhs.l), std::min(r, rhs.r));
}
inline LL area() const { return 1LL * std::max(0, b - t + 1) * std::max(0, r - l + 1); }
};
int pos[N];
std::vector<Rect> rect[N];
std::vector<LL> sum[N];
namespace SegTree {
#define ls o << 1
#define rs o << 1 | 1
int min[N << 2];
void update(int o, int l, int r, int x, int y) {
if (l == r) {
[o] = y; return;
min}
int m = l + r >> 1;
<= m ? update(ls, l, m, x, y) : update(rs, m + 1, r, x, y);
x [o] = std::min(min[ls], min[rs]);
min}
int ask(int o, int l, int r, int x) {
if (x < l) return n;
if (r <= x) return min[o];
int m = l + r >> 1;
return std::min(ask(ls, l, m, x), ask(rs, m + 1, r, x));
}
}
bool vis[N];
std::set<int> s;
[N]; // interval
PII nowint top[N];
void insert(int i, int v, int l, int r) {
if (s.count(v)) {
if (top[v] == i) {
assert(l == now[v].second + 1);
[v].second = r;
nowreturn;
}
= now[v].first;
l [v].emplace_back(top[v], i - 1, now[v].first, now[v].second);
rect[v].push_back(sum[v].back() + rect[v].back().area());
sum} else {
.insert(v);
s}
[v] = i, now[v].first = l, now[v].second = r;
top}
int main() {
(n);
readIntfor (int i = 0; i <= n; i++) rect[i].emplace_back(0, -1, 0, -1), sum[i].push_back(0);
= 0;
LL ans for (int i = 1, mex = 0, x, l, r, k; i <= n; i++) {
(x, l, r, k);
readInt= (x + ans) % (n + 1);
x = (l + ans) % i + 1;
l = (r + ans) % i + 1;
r = (k + ans) % (n + 1);
k if (l > r) std::swap(l, r);
(x, l, r, k);
dbg// append x
for (vis[x] = 1; vis[mex]; mex++) ;
::update(1, 1, n, i, x);
SegTreeif (pos[x]) SegTree::update(1, 1, n, pos[x], n);
[x] = i;
posauto it = s.find(x);
if (it != s.end()) {
.erase(it);
sauto [p, q] = now[x];
[x].emplace_back(top[x], i - 1, p, q);
rect[x].push_back(sum[x].back() + rect[x].back().area());
sumwhile (p <= q) {
int v = SegTree::ask(1, 1, n, q - 1), idx = smin(v, mex) ? 0 : pos[v];
// dbg(v, pos[v]);
(i, v, idx + 1, q);
insert= idx;
q }
}
(i, !x, i, i);
insert// ask k
auto &a = rect[k];
auto &b = sum[k];
auto qrect = Rect(l, r, l, r);
= 0;
ans // for (auto u: a) ans += (u & qrect).area();
int p = std::partition_point(a.begin(), a.end(), [&](const Rect &v) { return v.b < l || v.r < l; }) - a.begin();
int mp = std::partition_point(a.begin(), a.end(), [&](const Rect &v) { return v.l < l; }) - a.begin() - 1;
int mq = std::partition_point(a.begin(), a.end(), [&](const Rect &v) { return v.l <= r; }) - a.begin() - 1;
int q = std::partition_point(a.begin(), a.end(), [&](const Rect &v) { return v.t <= r && v.l <= r; }) - a.begin() - 1;
if (p > q) {
= 0;
ans } else if (p == q) {
= (a[p] & qrect).area();
ans } else {
= b[q] - b[p - 1];
ans -= a[p].area() - (a[p] & qrect).area();
ans -= a[q].area() - (a[q] & qrect).area();
ans ++, q--;
p(mp, q), smax(mq, mp + 1);
sminif (p <= mp) assert(a[p].l == a[mp].l), ans -= Rect(a[p].t, a[mp].b, a[p].l, l - 1).area();
if (mq <= q) assert(a[mq].l == a[q].l), ans -= b[q] - b[mq - 1] - Rect(a[mq].t, a[q].b, a[q].l, r).area();
}
if (s.count(k)) ans += (Rect(top[k], i, now[k].first, now[k].second) & qrect).area();
("%lld\n", ans);
printf}
return 0;
}
可持久化线段树
接上矩阵的思路。
看作每个数维护一个二维数组,每次矩阵内的数 \(+1\) ,然后询问一个矩阵的和。
这个可以用可持久化线段树维护。
可持久化线段树可以想象成一个矩阵 \(a[i][j]\),第一维是时间,第二维是区间。
类似于树状数组的区间加区间和的方法,维护差分 \(b[i][j] = a[i][j] - a[i - 1][j]\)。
即每次修改时,在 \(top\) 时间对区间 \((left,right)+1\),在 \(bottom + 1\) 时间对区间 \((left, right)-1\)
查询矩阵和(本题中时间和区间都是 \([l, r]\)),即 \[ \begin{aligned} &\sum_{i = l}^r\sum_{j = l}^r a[i][j]\\ = &\sum_{i = l}^r \sum_{k = 1}^i \sum_{j = l}^rb[k][j]\\ = &\sum_{k = 1}^r \sum_{i = k}^r \sum_{j = l}^rb[k][j]\\ = &\sum_{k = 1}^r (r - k + 1) \left(\sum_{j = l}^rb[k][j]\right)\\ &\{\text{note } s_k = \sum_{j = l}^rb[k][j]\}\\ = &(r + 1)\sum_{k = 1}^rs_k - \sum_{k = 1}^rk\cdot s_k \end{aligned} \] 所以直接在线段树上维护两个值 \(v, v \times time\) 就行了。
每个数都要维护可持久化线段树,要注意写法,容易爆内存(直接改上面的程序成功MLE了)。
#include <bits/stdc++.h>
#ifdef LOCAL
#define dbg(args...) std::cerr << "\033[32;1m" << #args << " -> ", err(args)
#else
#define dbg(...)
#endif
inline void err() { std::cerr << "\033[0m\n"; }
template<class T, class... U>
inline void err(const T &x, const U &... a) { std::cerr << x << ' '; err(a...); }
template <class T>
inline void readInt(T &w) {
char c, p = 0;
while (!isdigit(c = getchar())) p = c == '-';
for (w = c & 15; isdigit(c = getchar());) w = w * 10 + (c & 15);
if (p) w = -w;
}
template <class T, class... U>
inline void readInt(T &w, U &... a) { readInt(w), readInt(a...); }
template <class T, class U>
inline bool smin(T &x, const U &y) { return y < x ? x = y, 1 : 0; }
template <class T, class U>
inline bool smax(T &x, const U &y) { return x < y ? x = y, 1 : 0; }
typedef long long LL;
typedef std::pair<int, int> PII;
constexpr int N(2e5 + 5);
int n, pos[N];
namespace SegTree {
#define ls o << 1
#define rs o << 1 | 1
int min[N << 2];
void update(int o, int l, int r, int x, int y) {
if (l == r) {
[o] = y; return;
min}
int m = l + r >> 1;
<= m ? update(ls, l, m, x, y) : update(rs, m + 1, r, x, y);
x [o] = std::min(min[ls], min[rs]);
min}
int ask(int o, int l, int r, int x) {
if (x < l) return n;
if (r <= x) return min[o];
int m = l + r >> 1;
return std::min(ask(ls, l, m, x), ask(rs, m + 1, r, x));
}
#undef ls
#undef rs
}
struct Node {
*ls, *rs;
Node int v; LL vi;
, sumvi;
LL sumv} t[N * 100], *null = t, *ptr = t + 1;
class PerSegTree {
public:
(): root() { root[0] = null; }
PerSegTreevoid ins(int i, int l, int r, int v) {
auto it = root.rbegin();
assert(it->first <= i);
(root[i] = it->second, 1, n, l, r, v, i);
ins}
(int l, int r) {
LL ask= r + 1;
coef return ask((--root.upper_bound(r))->second, 1, n, l, r);
}
private:
std::map<int, Node*> root;
void ins(Node *&o, int l, int r, int x, int y, int v, int i) {
*ptr = *o, o = ptr++;
->sumv += 1LL * v * (y - x + 1);
o->sumvi += 1LL * v * i * (y - x + 1);
oif (x == l && r == y) {
->v += v, o->vi += 1LL * v * i;
oreturn;
}
int m = l + r >> 1;
if (y <= m)
(o->ls, l, m, x, y, v, i);
inselse if (x > m)
(o->rs, m + 1, r, x, y, v, i);
inselse
(o->ls, l, m, x, m, v, i), ins(o->rs, m + 1, r, m + 1, y, v, i);
ins}
int coef;
(Node *o, int l, int r, int x, int y) {
LL askif (x == l && r == y) return coef * o->sumv - o->sumvi;
int m = l + r >> 1;
= (1LL * coef * o->v - o->vi) * (y - x + 1);
LL ans if (y <= m)
+= ask(o->ls, l, m, x, y);
ans else if (x > m)
+= ask(o->rs, m + 1, r, x, y);
ans else
+= ask(o->ls, l, m, x, m) + ask(o->rs, m + 1, r, m + 1, y);
ans return ans;
}
};
bool vis[N];
std::map<int, PII> s;
int main() {
->ls = null->rs = null;
null(n);
readIntstd::vector<PerSegTree> t(n + 1);
= 0;
LL ans for (int i = 1, mex = 0, x, l, r, k; i <= n; i++) {
(x, l, r, k);
readInt= (x + ans) % (n + 1);
x = (l + ans) % i + 1;
l = (r + ans) % i + 1;
r = (k + ans) % (n + 1);
k if (l > r) std::swap(l, r);
(x, l, r, k);
dbgfor (vis[x] = 1; vis[mex]; mex++) ;
::update(1, 1, n, i, x);
SegTreeif (pos[x]) SegTree::update(1, 1, n, pos[x], n);
[x] = i;
posauto it = s.find(x);
if (it != s.end()) {
auto [p, q] = it->second;
.erase(it);
s[x].ins(i, p, q, -1);
twhile (p <= q) {
int v = SegTree::ask(1, 1, n, q - 1), idx = smin(v, mex) ? 0 : pos[v];
[v].ins(i, std::max(p, idx + 1), q, 1);
t[v] = PII(idx + 1, q);
s= idx;
q }
}
auto &[p, q] = s[!x];
? q = i : p = q = i;
p [!x].ins(i, i, i, 1);
t= t[k].ask(l, r);
ans ("%lld\n", ans);
printf}
return 0;
}
最后修改于 2021-08-13