0%

莫队(2)

不多说直接上题

P1903

给定一个颜色序列,支持一下两种操作:

  • 给定 $l,r$ 求区间 $[l,r]$ 内颜色数量。
  • 单点修改颜色。

带修莫队板子。

考虑在三维空间里移动询问点 $(l,r,t)$ 表示区间左右端点和处理过的询问的个数。那么就可以直接做,显然移动的复杂度都是 $O(1)$。

考虑排序时分块,设块长为 $b$,那么长度上会分为 $\dfrac nb$ 块。我们按照左端点编号第一关键字、右端点编号第二关键字、时间第三关键字来排序。假设询问次数与区间长度同阶,那么在左端点块编号不变时右端点会移动 $\dfrac nb\cdot b=n$ 次,因此右端点一共会移动 $nb$ 次,而左端点一共会移动 $nb$ 次,时间在两个块编号都不变的时候移动 $n$ 次,因此总共移动 $n\cdot\left(\dfrac nb\right)^2=\dfrac{n^3}{b^2}$ 次。我们平衡复杂度得到 $b=O(n^{\frac 23})$ 时复杂度最优,为 $O(n^{\frac53})$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define il inline
#define N 140000
il ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') {f=-1;} c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
struct Que {
int l,r,p,id;
Que() {l=r=p=id=0;}
Que(int _l,int _r,int _p,int _id) {l=_l,r=_r,p=_p,id=_id;}
};
int n,m,a[N],qtot,dtot,sq,rev[N],res,ans[N];
int cnt[1000005];
Que q[N],d[N];
pair<int,int> st[N];
il bool cmp(Que a,Que b) {
if(a.l/sq!=b.l/sq) return a.l/sq<b.l/sq;
if(a.r/sq!=b.r/sq) return a.r/sq<b.r/sq;
return a.p<b.p;
}
il void add(int idx) {
if(!cnt[a[idx]]) res++;
cnt[a[idx]]++;
}
il void del(int idx) {
cnt[a[idx]]--;
if(!cnt[a[idx]]) res--;
}
il void modify(int p,int v,int l,int r) {
if(l<=p&&p<=r) {
cnt[a[p]]--;
if(!cnt[a[p]]) res--;
cnt[v]++;
if(cnt[v]==1) res++;
}
a[p]=v;
}
int main() {
n=read(),m=read(),sq=pow(1.0*n,0.66666667);
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m;i++) {
char opt;scanf("%s",&opt);
int l=read(),r=read();
if(opt=='Q') ++qtot,q[qtot]=Que(l,r,dtot,qtot);
else ++dtot,d[dtot]=Que(l,r,0,i);
}
sort(q+1,q+qtot+1,cmp);
for(int i=1,l=1,r=0,t=0;i<=qtot;i++) {
while(l>q[i].l) add(--l);
while(l<q[i].l) del(l++);
while(r<q[i].r) add(++r);
while(r>q[i].r) del(r--);
while(t<q[i].p) t++,rev[t]=a[d[t].l],modify(d[t].l,d[t].r,l,r);
while(t>q[i].p) modify(d[t].l,rev[t],l,r),t--;
ans[q[i].id]=res;
}
for(int i=1;i<=qtot;i++) printf("%d\n",ans[i]);
return 0;
}

P4396

给定一个数列,多次给定区间 $[l,r]$ 和区间 $[p,q]$,求 $i\in[l,r]$ 时 $a_i\in[p,q]$ 的 $i$ 数量和 $a_i$ 数量。$n\le10^5$。

考虑朴素的方法是莫队之后转移时用树状数组维护权值区间即可,但是移动指针复杂度是 $O(\log n)$。

用值域分块维护上述过程,因为移动指针是单点修改因此复杂度是 $O(1)$ 的,而查询时虽然是 $O(\sqrt n)$ 但是乘在 $m$ 上不影响复杂度,总时间复杂度是 $O(n\sqrt n+m\sqrt n)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define il inline
#define N 100005
#define sqr 324
il ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') {f=-1;} c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
struct Que {
int l,r,a,b,id;
Que() {l=r=a=b=id=0;}
Que(int _l,int _r,int _a,int _b,int _id) {l=_l,r=_r,a=_a,b=_b,id=_id;}
};
int n,m,sq,a[N],ans[2][N];
int st[sqr],ed[sqr],belong[N],sum[2][sqr],val[2][N];
Que q[N];
il bool cmp(Que a,Que b) {
if(a.l/sq!=b.l/sq) return a.l/sq<b.l/sq;
if((a.l/sq)%2==0) return a.r<b.r;
return a.r>b.r;
}
il void modify(int id,int p,int v) {
val[id][p]+=v;
sum[id][belong[p]]+=v;
}
il int query(int id,int l,int r) {
int bl=belong[l],br=belong[r],ans=0;
if(bl==br) {
for(int i=l;i<=r;i++) ans+=val[id][i];
return ans;
}
for(int i=l;i<=ed[bl];i++) ans+=val[id][i];
for(int i=st[br];i<=r;i++) ans+=val[id][i];
for(int i=bl+1;i<br;i++) ans+=sum[id][i];
return ans;
}
il void add(int p) {
modify(0,a[p],1);
if(val[0][a[p]]==1) modify(1,a[p],1);
}
il void del(int p) {
modify(0,a[p],-1);
if(val[0][a[p]]==0) modify(1,a[p],-1);
}
int main() {
n=read(),m=read(),sq=sqrt(n);
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1,l,r,a,b;i<=m;i++) {
l=read(),r=read(),a=read(),b=read();
q[i]=Que(l,r,a,b,i);
}
sort(q+1,q+m+1,cmp);
sq=sqrt(100000);
for(int i=1;i<=sq;i++) st[i]=ed[i-1]+1,ed[i]=(n/sq)*i;
ed[sq]=100000;
for(int i=1;i<=sq;i++) {
for(int j=st[i];j<=ed[i];j++) belong[j]=i;
}
for(int i=1,l=1,r=0;i<=m;i++) {
while(l>q[i].l) add(--l);
while(l<q[i].l) del(l++);
while(r<q[i].r) add(++r);
while(r>q[i].r) del(r--);
ans[0][q[i].id]=query(0,q[i].a,q[i].b);
ans[1][q[i].id]=query(1,q[i].a,q[i].b);
}
for(int i=1;i<=m;i++) printf("%d %d\n",ans[0][i],ans[1][i]);
return 0;
}

SP10707

给定一棵树,每个节点有一个颜色,多次询问一段路径上的颜色种数。

树上莫队。使用括号序转化成序列上的问题。

我们 dfs 整棵树,在访问一个节点的时候将它压入括号序列,在离开它的子树之前再压一次。这样括号序列的长度恰好是 $2n$。记 $u$ 第一次和第二次出现的下标为 $st_u$ 和 $ed_u$。

考虑怎么提取 $u\to v$ 的路径。首先我们钦定 $st_u<st_v$。如果 $v$ 是 $u$ 的后代,那么就可以直接取 $[st_u,st_v]$ 作为计算区间(删除其中重复的元素);如果不是,那么就可以取 $[ed_u,st_v]\cup\operatorname{lca(u,v)}$ 作为计算区间(因为这一部分中既不包含 $st_l$ 又不包含 $ed_l$。同样要删除重复元素)。

那么就可以直接跑莫队啦,因为一个元素被计算两次就是没有,因此维护一个 $vis_u$ 代表有没有计算 $u$ 的贡献。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define il inline
#define N 100005
#define sqr 324
il ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') {f=-1;} c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
struct Que {
int l,r,lca,id;
Que() {l=r=lca=id=0;}
Que(int _l,int _r,int _lca,int _id) {l=_l,r=_r,lca=_lca,id=_id;}
};
int n,m,sq,col[N],bin[N],tot,res,ans[N];
int dfn[N],rev[N],fa[N][17],dep[N];
int el[N],len,st[N],ed[N];
vector<int> g[N];
Que q[N];
il void dfs(int u,int f) {
dep[u]=dep[f]+1;
el[++len]=u,fa[u][0]=f;
for(int i=1;i<=16;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
dfn[u]=++tot,rev[tot]=u;
st[u]=len;
for(auto v:g[u]) {
if(v==f) continue;
dfs(v,u);
}
el[++len]=u;
ed[u]=len;
}
il int LCA(int u,int v) {
if(dep[u]<dep[v]) swap(u,v);
for(int i=16;i>=0;i--) if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return u;
for(int i=16;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
il bool cmp(Que a,Que b) {
if(a.l/sq!=b.l/sq) return a.l/sq<b.l/sq;
if((a.l/sq)%2==0) return a.r<b.r;
return a.r>b.r;
}
int cnt[N];
bool vis[N];
il void add(int u) {
if(!cnt[col[u]]) res++;
cnt[col[u]]++;
}
il void del(int u) {
cnt[col[u]]--;
if(!cnt[col[u]]) res--;
}
il void calc(int u) {
if(vis[u]) del(u);
else add(u);
vis[u]^=1;
}
int main() {
n=read(),m=read(),sq=sqrt(2*n);
for(int i=1;i<=n;i++) col[i]=bin[i]=read();tot=n;
sort(bin+1,bin+tot+1),tot=unique(bin+1,bin+tot+1)-bin-1;
for(int i=1;i<=n;i++) col[i]=lower_bound(bin+1,bin+tot+1,col[i])-bin;
tot=0;
for(int i=1,u,v;i<n;i++) {
u=read(),v=read();
g[u].push_back(v),g[v].push_back(u);
}
dfs(1,1);
for(int i=1,u,v;i<=m;i++) {
u=read(),v=read();
if(st[u]>st[v]) swap(u,v);
int l=LCA(u,v);
if(l==u) q[i]=Que(st[u],st[v],0,i);
else q[i]=Que(ed[u],st[v],l,i);
}
sort(q+1,q+m+1,cmp);
for(int i=1,l=1,r=0;i<=m;i++) {
while(l>q[i].l) calc(el[--l]);
while(r<q[i].r) calc(el[++r]);
while(l<q[i].l) calc(el[l++]);
while(r>q[i].r) calc(el[r--]);
if(q[i].lca) calc(q[i].lca);
ans[q[i].id]=res;
if(q[i].lca) calc(q[i].lca);
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}

P3709

定义一个数组的权值的计算方式如下:

有一个可重集合 $S$,初始为空。还有一个变量 $cnt$ 初始为 $1$。

每一步你可以选择一个数 $x$,在原数组中删除它。如果存在 $y\in S$ 且 $y\ge x$,那么 $cnt\leftarrow cnt+1$ 并且清空 $S$。最后将 $x$ 放入 $S$。

权值是所有放入集合顺序中 $cnt$ 可能的最小值。

有一个长度为 $n$ 的数组和 $m$ 次查询,每次询问子数组 $a_l,a_{l+1},\dots,a_r$ 的价值。

说人话就是区间众数的出现次数。

离线可以莫队。在线就是蒲公英。

首先加数是好做的,直接维护每个数出现的次数和 $ans$ 取 max 即可。删除的时候需要维护 $sum_i$ 表示出现次数为 $i$ 的个数。如果删除的那个权值出现次数不是最大的那么没有影响;如果是且是唯一的那么 $ans$ 要减 $1$,否则没有影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define il inline
#define N 200005
il ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') {f=-1;} c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
struct Que {
int l,r,id;
Que() {l=r=id=0;}
Que(int _l,int _r,int _id) {l=_l,r=_r,id=_id;}
};
Que q[N];
int n,m,tot,sq,a[N],b[N];
int ans[N],res,cnt[N],sum[N];
il bool cmp(Que a,Que b) {
if(a.l/sq!=b.l/sq) return a.l/sq<b.l/sq;
if((a.l/sq)%2==0) return a.r<b.r;
return a.r>b.r;
}
il void add(int p) {
sum[cnt[a[p]]]--;
cnt[a[p]]++;
sum[cnt[a[p]]]++;
res=max(res,cnt[a[p]]);
}
il void del(int p) {
if(res==cnt[a[p]]&&sum[cnt[a[p]]]==1) res--;
sum[cnt[a[p]]]--;
cnt[a[p]]--;
sum[cnt[a[p]]]++;
}
int main() {
n=read(),m=read(),sq=sqrt(n);
for(int i=1;i<=n;i++) b[i]=a[i]=read();
sort(b+1,b+n+1);tot=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
for(int i=1,l,r;i<=m;i++) {
l=read(),r=read();
q[i]=Que(l,r,i);
}
sort(q+1,q+m+1,cmp);
for(int i=1,l=1,r=0;i<=n;i++) {
while(l>q[i].l) add(--l);
while(r<q[i].r) add(++r);
while(l<q[i].l) del(l++);
while(r>q[i].r) del(r--);
ans[q[i].id]=res;
}
for(int i=1;i<=m;i++) printf("-%d\n",ans[i]);
return 0;
}

P5906

给定一个数组和 $m$ 次询问,每次询问查询区间内两个相同的数的最远距离,定义为下标差的绝对值。

增加点非常好做,我们维护每个数出现的最左最右两个位置,然后直接修改并更新答案即可。

删除非常难做,但是我们可以利用回滚莫队规避删除操作。

我们枚举当前操作左端点的块编号并且在到达一个新块 $[L,R]$ 的时候重置左端点 $l=R+1$、右端点 $r=R$(是个空区间)。如果当前询问两个端点块编号相同那么直接暴力求答案;否则我们先将右端点扩展到对应的端点,然后记录下来当前的状态,然后将左端点向左扩展到对应位置,记录答案,再将之前记录的状态全部还原(称为回滚),并将 $l$ 重置为 $R+1$。

显然回滚的复杂度也是块长的,因此复杂度还是 $O(m\sqrt n)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define il inline
#define N 200005
#define sqr 450
il ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') {f=-1;} c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
struct Que {
int l,r,id;
Que() {l=r=id=0;}
Que(int _l,int _r,int _id) {l=_l,r=_r,id=_id;}
};
Que q[N];
int n,m,a[N],bin[N],tot,sq,tmp;
int st[sqr],ed[sqr],belong[N];
int ans[N],maxn[N],minn[N];
il bool cmp(Que a,Que b) {
if(belong[a.l]!=belong[b.l]) return belong[a.l]<belong[b.l];
return a.r<b.r;
}
il void add(int p) {
if(!minn[a[p]]) minn[a[p]]=maxn[a[p]]=p;
else maxn[a[p]]=p,tmp=max(tmp,p-minn[a[p]]);
}
int main() {
n=read(),sq=sqrt(n);
for(int i=1;i<=n;i++) a[i]=read(),bin[i]=a[i];
for(int i=1;i<=sq;i++) st[i]=ed[i-1]+1,ed[i]=(n/sq)*i;
ed[sq]=n;
for(int i=1;i<=sq;i++) for(int j=st[i];j<=ed[i];j++) belong[j]=i;
tot=n;sort(bin+1,bin+tot+1);tot=unique(bin+1,bin+tot+1)-bin-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(bin+1,bin+tot+1,a[i])-bin;
m=read();
for(int i=1,l,r;i<=m;i++) {
l=read(),r=read();
q[i]=Que(l,r,i);
}
sort(q+1,q+m+1,cmp);
int l=0,r=0,lblk=0;
for(int i=1;i<=m;i++) {
if(belong[q[i].l]==belong[q[i].r]) {
tmp=0;
for(int j=q[i].l;j<=q[i].r;j++) minn[a[j]]=0;
for(int j=q[i].l;j<=q[i].r;j++) {
if(!minn[a[j]]) minn[a[j]]=j;
else tmp=max(tmp,j-minn[a[j]]);
}
for(int j=q[i].l;j<=q[i].r;j++) minn[a[j]]=0;
ans[q[i].id]=tmp;
continue;
}
if(lblk!=belong[q[i].l]) {
while(r>ed[belong[q[i].l]]) maxn[a[r]]=minn[a[r]]=0,r--;
while(l<ed[belong[q[i].l]]+1) maxn[a[l]]=minn[a[l]]=0,l++;
r=l-1;
lblk=belong[q[i].l];
tmp=0;
}
while(r<q[i].r) add(++r);
int pl=l,res=tmp;
while(pl>q[i].l) {
--pl;
if(!maxn[a[pl]]) maxn[a[pl]]=pl;
else res=max(res,maxn[a[pl]]-pl);
}
ans[q[i].id]=res;
while(pl<l) {
if(maxn[a[pl]]==pl) maxn[a[pl]]=0;
pl++;
}
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}