数组的划分 题解
bamboo12345 · · 题解
原本这里是一道简单题的,但是因为本人突然有灵感出了这道题,恶心了别人,也狠狠地恶心了自己。
作为被小 e 评价创新程度 < A 的题,这个题确实比较烂,求轻喷()。
题目描述:很形式化了,自己看吧。
做法:
我们分部分分一档档讲解。
首先注意到一个观察,如果我们要求
测试点 1~3(12pts)
模拟这个观察,每次暴力到
强制限定操作打标记即可,赋值操作直接模拟。
复杂度
测试点 4,12(8pts)
这个给的很良心了,也有助于出正解的一档部分分,这组部分分的特点在于
结论是
注意到这个数据随机,对于做法会很有用。
测试点 4~11(32pts)
这几个数据点的限制在于
那么我们发现
测试点 5,13,14(12pts)
这几个数据点的限制在于没有 1,2 操作,也就意味着没有修改,只有询问。
我们发现,每次我们对于一个
测试点 5 可以直接暴力按这个东西模拟,但是对于 13,14,我们需要用 SAM 预处理出来这个
测试点 6\~7,15\~17(20pts)
这几个数据点的限制在于没有 2 操作。
我们注意到一件事情,如果
比如这里,长为 10 的数组中,我们切分了 4,6 的位置,那么计算
那么我们发现,我们询问的答案肯定是两端的散块加上中间强制限定切出来的整块,我们就可以维护这些切出来的整块的答案,然后散块重新计算。
具体的,我们可以用一个 set 维护强制限定的位置,然后每个 set 里的后驱。然后开一个树状数组维护这些整块的贡献,就可以快速计算中间这些整块的答案。
复杂度
测试点 1~25(100pts)
终于要到正解了,你会发现暴力打满其实有 68 分,很不错了。
我们发现,如果仅仅是用倍增维护
首先我们观察到一件事情,对于直接的
那么我们其实可以想到这个题 弹飞绵羊。我们也是类似的做法,进行分块。这里也可以用 LCT 直接做,但是我不太会写我觉得常数比较大,并且我喜欢根号数据结构。我们直接重构这些修改的块即可。
然后也是类似于 弹飞绵羊 一题,我们维护跳到下一个块的位置和次数,对于询问先跳到相同的块,再暴力跳即可。
做完了吗?其实没有,这个也在我自己写 std 的时候坑了我一下。我们注意到,对于修改的时候,我们还需要把强制限定的整块的贡献更新,这样整个题就做完了。
简单的分析一下复杂度,维护强制限定是
可以使用 lct 来做到
当然用一些其他的数据结构比如线段树之类的也很容易做到
值得一提的是,发现很多选手写的都是 hash 维护出现的段数,这样也是可以的,就是空间会多个
代码:
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
const int maxn = 2e5 + 5;
int n, q, m, l[maxn], r[maxn], pos[maxn], len;
int nxt[maxn], nxtb[maxn], dis[maxn];
int t[maxn];
struct node {
map<int, int> nxt;
int lnk, len;
};
struct SAM { // SAM,用来处理 nxt
node tr[maxn];
int lst = 1, tot = 1;
void add(int c) {
int cur = ++tot, p = lst;
tr[cur].len = tr[p].len + 1;
while(p && !tr[p].nxt[c])
tr[p].nxt[c] = cur, p = tr[p].lnk;
if(!p)
tr[cur].lnk = 1;
else {
int q = tr[p].nxt[c];
if(tr[p].len + 1 == tr[q].len)
tr[cur].lnk = q;
else {
int clone = ++tot;
tr[clone] = tr[q], tr[clone].len = tr[p].len + 1;
while(p && tr[p].nxt[c] == q)
tr[p].nxt[c] = clone, p = tr[p].lnk;
tr[cur].lnk = tr[q].lnk = clone;
}
}
lst = cur;
}
} sam;
void init() { // 处理 nxt
int p = 1, len = 0, ps = 1;
for (int i = 1; i <= n; i++) {
if(len) { // 减去 i - 1
if(sam.tr[sam.tr[p].lnk].len + 1 == len) // 考虑会不会移动节点
p = sam.tr[p].lnk;
len--;
}
while(ps <= n && sam.tr[p].nxt[t[ps]]) //如果能往后
len++, p = sam.tr[p].nxt[t[ps]], ps++;
nxt[i] = i + len; // [i, i + len - 1] 是一段,跳到 i + len
}
}
struct Tree_array {
int tr[maxn], n;
void add(int x, int val) {
while(x <= n)
tr[x] += val, x += lowbit(x);
}
int query(int x) {
int ans = 0;
while(x)
ans += tr[x], x -= lowbit(x);
return ans;
}
} tree;
void build() { // 分块
len = sqrt(n);
if(n <= 50)
len = n;
else
len = max(len, 50);
for (int i = 1; i <= n; i++) {
pos[i] = (i - 1) / len + 1;
if(!l[pos[i]])
l[pos[i]] = i;
r[pos[i]] = i;
}
}
void buildblock(int p) {// 重构编号为 p 的块
for (int i = r[p]; i >= l[p]; i--) {
if(nxt[i] > r[p])
nxtb[i] = nxt[i], dis[i] = 1;
else
nxtb[i] = nxtb[nxt[i]], dis[i] = dis[nxt[i]] + 1;
}
}
int queryin(int l, int r) { //询问区间中无强制限定的答案
if(l > r)
return 0;
int p = pos[l], q = pos[r], ans = 0, nw = l;
for (int i = p; i < q; i++)
ans += dis[nw], nw = nxtb[nw];
while(nw <= r)
ans++, nw = nxt[nw];
return ans;
}
int val[maxn];
set<int> s;
int queryall(int l, int r) { // 询问区间中有强制限定的答案
set<int>::iterator itl = s.lower_bound(l), itr = s.lower_bound(r);
itr--;
if(*itl >= r)
return queryin(l, r);
int ans = queryin(l, *itl) + queryin(*itr + 1, r);
if(*itl == *itr)
return ans;
itr--;
return ans + tree.query(*itr + 1) - tree.query(*itl);
}
void add(int x) { // 加入一个强制限定
set<int>::iterator itl = s.lower_bound(x), itr = s.upper_bound(x);
itl--;
// 清除 [l+1,r],加入 [l+1,x],[x+1,r]
tree.add(*itl + 1, -val[*itl + 1]);
val[*itl + 1] = queryin(*itl + 1, x);
tree.add(*itl + 1, val[*itl + 1]);
val[x + 1] = queryin(x + 1, *itr);
tree.add(x + 1, val[x + 1]);
s.insert(x);
}
void del(int x) { // 删除一个强制限定
set<int>::iterator it = s.find(x), itl = it, itr = it;
itl--, itr++;
tree.add(x + 1, -val[x + 1]);
val[x + 1] = 0;
tree.add(*itl + 1, -val[*itl + 1]);
val[*itl + 1] = queryin(*itl + 1, *itr);
tree.add(*itl + 1, val[*itl + 1]);
s.erase(x);
}
int t0[maxn], id;
void renew(int l, int r) { // 因为 nxt 更新了,所以也要重新维护 set 的贡献
set<int>::iterator itl = s.lower_bound(l), itr = itl;
itl--;
while(*itl < r) {
tree.add(*itl + 1, -val[*itl + 1]);
val[*itl + 1] = queryin(*itl + 1, *itr);
tree.add(*itl + 1, val[*itl + 1]);
itl++, itr++;
}
}
void change(int l, int r) {
for (int i = l; i <= r; i++)
t[i] = t0[i];
l = max(l - 50, 1);
int p = 1, len = 0, ps = l;
for (int i = l; i <= r; i++) { // 类似预处理
if(len) {
if(sam.tr[sam.tr[p].lnk].len + 1 == len)
p = sam.tr[p].lnk;
len--;
}
while(ps <= n && sam.tr[p].nxt[t[ps]])
len++, p = sam.tr[p].nxt[t[ps]], ps++;
nxt[i] = i + len;
}
for (int i = pos[l]; i <= pos[r]; i++)
buildblock(i);
renew(l, r);
}
int read() {
int sum = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c))
sum = sum * 10 + c - '0', c = getchar();
return sum;
}
void write(int x) {
if(x <= 9) {
putchar(x + '0');
return ;
}
write(x / 10);
putchar(x % 10 + '0');
}
int main() {
// freopen("test.in", "r", stdin);
// freopen("baoli.out", "w", stdout);
int V = 0;
n = read(), m = read(), q = read(), id = read();
for (int i = 1; i <= m; i++) {
int k = read();
sam.lst = 1;
for (int j = 1; j <= k; j++) {
int x = read();
sam.add(x);
V = max(V, x);
}
}
for (int i = 1; i <= n; i++)
t[i] = read(), V = max(V, t[i]);
init(), tree.n = n;
build();
for (int i = pos[1]; i <= pos[n]; i++)
buildblock(i);
s.insert(0), s.insert(n); // 插入 0,n,防爆
while(q--) {
int op = read();
if(op == 1) { // 限定
int x = read();
if(val[x + 1])
del(x);
else
add(x);
}
if(op == 2) { // 修改
int l, r;
l = read(), r = read();
while(l > r);
for (int i = l; i <= r; i++)
t0[i] = read();
change(l, r);
}
if(op == 3) { // 询问
int l, r;
l = read(), r = read();
while(l > r);
write(queryall(l, r));
putchar('\n');
}
}
return 0;
}