Training for 分块&莫队
分块 Study data Link:http://hzwer.com/8053.html // hzwer讲的很好很全
树上莫队Study Link:http://codeforces.com/blog/entry/43230 ( ery nice 并且有题目推荐
莫队算法时间复杂度O( n * sqrt(n) )证明:
由于每一块的大小为sqrt(n),故有sqrt(n)块,按照所属块为first key ,右端点为second key,考虑每一块,因为每块的右端点是Increase,故右端点最大的移动为N ,左端点的最大移动为 k × sqrt(n),故总复杂度为O(n×sqrt(n) + m×sqrt(n)) ≈ O( n * sqrt(n) )
一道很牛逼的题
题意
给一个n的数的序列,现有q次询问, 1代表是修改操作,2代表是查询操作,对于每次查询输出[1,1e6]以内出现偶数次最小的树(没有出现也算是偶数次) (a[i],n,q<=1e6)
分析
对[1,1e6]分块,每次修改之前的数所在的块和修改后所在的块即可
代码:待补
CDOJ 1324
分析
简单的单点更新,区间最大值
时间复杂度( n×sqrt(n) )
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<vector> #define ll long long using namespace std; const int maxn = 1e5+7; //blcok:每块的大小 //num:块的数量 //l[]:每块的左边界,r[]:每块的右边界 //belong[]:每个位置的数属于那一块 int n,q; int belong[maxn],block,l[maxn],r[maxn],num; ll a[maxn],Max[maxn]; void build(){ block=sqrt(n); num=n/block; if(n%num) num++; for(int i=1;i<=num;i++) l[i]=(i-1)*block, r[i]=i*block; // 处理出每块的左右边界 r[num]=n; for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1; // 每个位置的数属于那一块 for(int i=1;i<=num;i++) for(int j=l[i];j<=r[i];j++) Max[i]=max(Max[i],a[j]); } void update(int x,int y){ a[x]+=y; Max[belong[x]]=max(Max[belong[x]],a[x]); } ll ask(int x,int y) { ll ans=0; if(belong[x]==belong[y]){ for(int i=x;i<=y;i++) ans=max(ans,a[i]); } else { for(int i=x;i<=r[belong[x]];i++) ans=max(ans,a[i]); for(int i=belong[x]+1;i<=belong[y]-1;i++) ans=max(ans,Max[i]); for(int i=l[belong[y]];i<=y;i++) ans=max(ans,a[i]); } return ans; } int main() { scanf("%d%d", &n, &q); build(); for(int i=1;i<=q;i++) { int op,l,r; scanf("%d%d%d", &op, &l, &r); if(op==1) update(l,r); else printf("%lld\n", ask(l,r)); } return 0; }
莫队入门
codeforces 617E. XOR and Favorite Number
题意
给你一个n个数的序列,有m次询问,每次询问问你 L,R, K,区间[l,r] ,有多少对(i,j)( l ≤ i ≤ j ≤ r )使得 the xor of the numbers ai, ai + 1, ..., aj is equal to k.
(1 ≤ n, m ≤ 1e5, 0 ≤ k ≤ 1e6,0 ≤ ai ≤ 1e6)
分析
由于xor具有 A^A=0性质,处理出xor前缀,区间[L,R]的xor值即为pre[R]^pre[L-1],直接上莫队,一边更新区间[L,R]每个异或值出现的次数,统计时每次只需要询问修改的区间更新(加/减)即可
trick:需要注意更新的先后顺序以及处理[L,R]区间,由于xor的特殊性,需要处理L-1 !!!
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 3e6 +7; int belong[maxn],a[maxn],num,block, ans[maxn]; ll f[maxn]; int n,m,k; struct node { int l,r,id; friend bool operator < (node a,node b) { if(belong[a.l] == belong[b.l]) return a.r<b.r; return belong[a.l] < belong[b.l]; } }q[maxn]; ll Ans=0; void del(int p) // 因为删除的这个位置不在考虑的区间内,所以先更新ans,在查询 { ans[a[p]]--; Ans-=ans[a[p]^k]; } void add(int p) // 因为加的这个数如果恰好为L-1,这个应不作为考虑,不是L-1的话,这个位置会在下次统计上,所以先查询后更新 { Ans+=ans[a[p]^k]; ans[a[p]]++; } void solve() { int L=1,R=0; for(int i=1;i<=m;i++) { while(L<q[i].l) // 由于要得到A[l-1]^A[r] , 故应该先更新,再L++; { del(L-1); L++; } while(L>q[i].l){ //当L>q[i].l ,为了便于思考取L=q[i].l+1,其实已经处理了q[i].l这个位置,因为上次统计的时候利用了这个位置 L--; add(L-1); } while(R<q[i].r){ // 右端点没有什么特殊的 R++; add(R); } while(R>q[i].r){ del(R); R--; } f[q[i].id]=Ans; } } int main() { ans[0]=1; // 注意什么都不取得时候也有一种情况,当A[t]^K=0时,也有一种情况,L=0时 scanf("%d%d%d", &n, &m, &k); block=int(sqrt(n)); for(int i=1;i<=n;i++){ scanf("%d", &a[i]); a[i]^=a[i-1]; belong[i]=(i-1)/block+1; } for(int i=1;i<=m;i++){ scanf("%d%d", &q[i].l, &q[i].r); q[i].id=i; } sort(q+1,q+m+1); solve(); for(int i=1;i<=m;i++) printf("%lld\n", f[i]); return 0; }
分析
莫队板子题
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 1e6+7; int belong[maxn],a[maxn],num,block, ans[maxn]; ll fz[maxn],fm[maxn]; int n,m,k; struct node{ int l,r,id; friend bool operator < (node a,node b) { if(belong[a.l] == belong[b.l]) return a.r<b.r; return belong[a.l] < belong[b.l]; } }q[maxn]; ll gcd(ll a, ll b) { return b==0? a : gcd(b, a%b); } ll Ans=0; void del(int p) { Ans-=ans[a[p]]-1; ans[a[p]]--; } void add(int p) { ans[a[p]]++; Ans+=ans[a[p]]-1; } void solve() { int L=1,R=0; for(int i=1;i<=m;i++) { while(L<q[i].l) { del(L); L++; } while(L>q[i].l) { L--; add(L); } while(R<q[i].r) { R++; add(R); } while(R>q[i].r) { del(R); R--; } ll k=((1LL)*(q[i].r-q[i].l+1))*(q[i].r-q[i].l)/2; if(Ans!=0) { ll g=gcd(Ans,k); fz[q[i].id]=Ans/g; fm[q[i].id]=k/g; } else { fz[q[i].id]=Ans; fm[q[i].id]=1; } } } int main() { scanf("%d%d", &n, &m, &k); block=int(sqrt(n)); for(int i=1;i<=n;i++){ scanf("%d", &a[i]); belong[i]=(i-1)/block+1; } for(int i=1;i<=m;i++){ scanf("%d%d", &q[i].l, &q[i].r); q[i].id=i; } sort(q+1,q+m+1); solve(); for(int i=1;i<=m;i++) printf("%lld/%lld\n", fz[i],fm[i]); return 0; }
codeforces 620 F. Xors on Segments
题意
给一个n个数的序列,给出m个询问 l, r, 问从区间[L,R]中选两个数 a,b(a<=b,可以同一个数),f=a^(a+1)....^(b-1)^b 的最大值(1 ≤ n ≤ 5e4, 1 ≤ m ≤ 5e3 ,1 ≤ ai ≤ 1e6,1<=L<=R<=n)
分析
数据水了放过了n^2,但数组开太大也会T,正解:莫队+Tire
solution:暴力的复杂度是(m×n^2)肯定gg,那么考虑优化,对所有的询问离线,dp[i]: 以a[i]作为开头的元素的最大值,询问所有 j > i,当以 j 为右端点的询问的左端点<= i时更新
时间复杂度:O(n^2 + n×m)
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 5e4+7; int a[maxn],pre[1000007],n,m; vector<pair<int,int> >v[maxn]; int ans[maxn]; int main() { for(int i=1;i<1000007;i++) pre[i]=pre[i-1]^i; scanf("%d%d",&n, &m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } int l,r; for(int i=1;i<=m;i++) { scanf("%d%d", &l, &r); v[r].push_back({l,i}); } int dp; for(int i=1;i<=n;i++) { dp=0; for(int j=i;j<=n;j++) { dp=max(dp, pre[a[j]] ^ pre[a[i]] ^ min(a[i],a[j])); for(auto it : v[j]) { if(it.first<=i) ans[it.second]=max(ans[it.second],dp); } } } for(int i=1;i<=m;i++) { printf("%d\n",ans[i]); } return 0; }
BZOJ 1086 糖果公园 DFS+贪心
分析
dfs的时候统计子树叶子数量,大于等于b时归为一个省,省会就为当前父节点,怎么把子树节点保存下来呢?用一个栈即可。最后剩下小于b直接给最后一个省即可
#include<iostream> #include<cstdio> using namespace std; const int maxn = 1000+2; int belong[maxn],q[maxn],top,sz[maxn],cap[maxn],sum; int head[maxn],nxt[maxn*2],to[maxn*2],tot,k; int n,b; void addedge(int u,int v) { to[++tot]=v; nxt[tot]=head[u]; head[u]=tot; } void dfs(int x,int fa) { q[++top]=x; for(int i=head[x];i;i=nxt[i]) { int v=to[i]; if(v!=fa) { dfs(v,x); if(sz[x]+sz[v]>=b) { sz[x]=0; cap[++sum]=x; //cout<<v<<' '<<x<<' '<<sum<<endl; while(q[top]!=x) { belong[q[top]]=sum; --top; } } else sz[x]+=sz[v]; } } sz[x]++; } int main() { scanf("%d%d", &n, &b); if(n<b){puts("0");return 0;} int u,v; for(int i=1;i<=n-1;i++){ scanf("%d%d", &u, &v); addedge(u,v); addedge(v,u); } dfs(1,0); if(n==1) cout<<1<<endl<<1<<endl<<1<<endl; else { printf("%d\n",sum); for(int i=1;i<=n;i++) { if(!belong[i]) belong[i]=sum; } for(int i=1;i<=n;i++) printf("%d%c", belong[i], i==n ? '\n':' '); for(int i=1;i<=sum;i++) printf("%d%c", cap[i], i==n?'\n':' '); } return 0; }
题意
在x轴上,给n个区间 [ Li, Ri ],m个询问,每个询问包含k个点,对于每个询问输出这些点在几个区间里 (n,m<=3e5, L,R<= 1e6,所有询问 k 的和不超过 3e5 )
分析
正着做,无论是对于每个点考虑在那些区间 和 那些区间包括那些点都没有想到合适复杂度,换个角度,考虑点的补集所组成的线段集合完全包含那些线段,那么这些线段对答案一定没有贡献,
故那么我们怎么check每个询问点的补集所组成的线段和所给线段的关系?考虑对所有询问离线,按照线段左端点为first key(从大到小), 右端点为second key(从小到大),每个线段的询问时间为third
key(从小到大,因为当已经线段和询问线段重合的情况),这样可以保证每次询问的左端点是递减的,考虑用树状数组加速这个过程,每次更新右端点即可,依次更新即可
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 1e6+7; int c[maxn]; int ans[maxn]; struct node{ int l,r,id; friend bool operator < (node a,node b) { if(a.l != b.l) return a.l>b.l; else if(a.r!=b.r) return a.r < b.r; else return a.id<b.id; } }t[maxn]; int lowbit(int x){ return x&(-x); } int sum(int x){ int sum=0; while(x>0){ sum+=c[x]; x-=lowbit(x); } return sum; } int modfiy(int x,int val){ while(x<=1000000){ c[x]+=val; x+=lowbit(x); } } int n,q; int main() { scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { scanf("%d%d", &t[i].l,&t[i].r); t[i].id=0; } int k; int tot=n; for(int i=1;i<=q;i++) { scanf("%d", &k); int st=0; for(int j=1;j<=k;j++) { int x; scanf("%d", &x); if(!st) { if(x>1) { t[++tot].l=1; t[tot].r=x-1; t[tot].id=i; } st=x; } else { if(x-1<st+1) { } else { t[++tot].l=st+1; t[tot].r=x-1; t[tot].id=i; } st=x; } } if(st+1<=1000000) { t[++tot].l=st+1; t[tot].r=1000000; t[tot].id=i; } } sort(t+1, t+tot+1); for(int i=1;i<=tot;i++) { if(t[i].id==0) { modfiy(t[i].r,1); } else { ans[t[i].id]+=sum(t[i].r); } } for(int i=1;i<=q;i++) printf("%d\n", n-ans[i]); return 0; }
Codeforces 375D 树上莫队
题意
给一颗n个节点带权树(权值=颜色),现有m个询问,每个询问问u,k,问以u为根的子树,出现的所有颜色中次数>=k次的数量(根为1,n,m<=1e5 )
分析
Study Link:dsu on tree
解法:莫队/dsu on tree+bit
莫队:求出dfs序,可知每个点的子树的范围,转化为区间上的序列的莫队,每次询问一个根节点相当于询问它的子树区间,对区间分块排序后直接上莫队即可 ( 类比区间
trick:处理>=k的数量很巧妙啊,因为每个颜色数量是逐渐变大的,故直接用一个数组递推过去即可,O(1)
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 1e5+7; int n,m,col[maxn],rcol[maxn],sum[maxn],cnt[maxn]; int head[maxn*2],nxt[maxn*2],to[maxn*2],tot; int L[maxn],R[maxn], Ans[maxn], block, belong[maxn]; void addedge(int u,int v) { to[++tot]=v; nxt[tot]=head[u]; head[u]=tot; } struct node{ int l,r,id,k; friend bool operator <(node a,node b) { if(a.l/block!=b.l/block) return a.l/block < b.l/block; return a.r<b.r; } }q[maxn]; int tms; void dfs(int u,int fa){ L[u]=++tms; for(int i=head[u];i;i=nxt[i]){ int v=to[i]; if(v!=fa) dfs(v,u); } R[u]=tms; } int main() { scanf("%d%d", &n, &m); for(int i=1;i<=n;i++) scanf("%d", &col[i]); block=int(sqrt(n)); int u,v; for(int i=1;i<=n-1;i++){ scanf("%d%d", &u, &v); addedge(u,v); addedge(v,u); } dfs(1,0); int x,kk; for(int i=1;i<=m;i++){ scanf("%d%d", &x, &kk); q[i].l=L[x]; q[i].r=R[x]; q[i].k=kk; q[i].id=i; } sort(q+1,q+m+1); for(int i=1;i<=n;i++) rcol[i]=col[i]; for(int i=1;i<=n;i++) col[L[i]]=rcol[i]; int L=1,R=0; for(int i=1;i<=m;i++) { while(L<q[i].l) { sum[cnt[col[L]]]--; cnt[col[L]]--; L++; } while(L>q[i].l) { L--; cnt[col[L]]++; sum[cnt[col[L]]]++; } while(R<q[i].r) { R++; cnt[col[R]]++; sum[cnt[col[R]]]++; } while(R>q[i].r) { sum[cnt[col[R]]]--; cnt[col[R]]--; R--; } Ans[q[i].id]=sum[q[i].k]; } for(int i=1;i<=m;i++) printf("%d\n", Ans[i]); return 0; }
分析
留坑,树上带修改莫队 ,vfk题解Link
树上莫队 SPOJ COT2 - Count on a tree II / BZOJ 3757
题意
SPOJ:给一颗点权数,多次询问路径(a,b)上有多少个权值不同的点(n<=4e5,m<=1e6)
BZOJ:
分析
直观的做法:每次从lca(a,b)分别走到a,b即可,但首先从lca(a,b)出发不能保证走的一定是a,b的路径,最坏的情况是走整棵树,gg
莫队:考虑深搜将树分块,将树上的序列转化成区间上的序列,按照区间上的莫队方法来做
转移:考虑从(u,v)->(u',v')
设S(u,v):u到v路径上的点集,则S(u,v)=S(root,u) xor S(root,v) xor lca(u,v) ( xor运算=对称差:简单的说就是去掉出现两次的 )
由于lca(u,v)很麻烦,那么我们设T(u,v)=S(root,u) xor S(root,v),则T(u',v)=S(root,u') xor S(root,v) (现考虑一个点移动)
T(u',v) xor T(u,v) = S(root,u') xor S(root,v)xor S(root,u) xor S(root,v)
T(u',v) xor T(u,v) = S(root,u') xor S(root,u) = T(u',u)
两边同时 xor T(u,v)
T(u',v) = T(u,v) xor T(u',u)
=> S(a',b) = S(a,b) xor S(a,a') xor lca(a',b)
=> S(a',b')=S(a,b) xor S(a,a') xor S(b,b') xor lca(a',b')
所以:从(a,b)->(a',b') 只需要 a-> a' 和 b-> b'路径上的点取反 ( 除lca(u',v) )的情况取反即可
转移的方法:通过预处理的深度,不断调节深度,直至到达两点lca,但没有处理lca
trick:注意lca(u',v')不是取反,直接取相反的情况即可 ( 即没有对lca(u',v')的数量修改),因为在转移的时候并没有将lca(u',v')考虑进去,
那么有一个问题,若lca(u,v)==lca(u',v'),这样处理没问题,但lca(u,v) != lca(u',v'),其实通过画图观察不难发现,u->u'和v->v' 转移过程都没有改变lca(u,v),故不影响
所以我们只要大胆的把u->u'和v->v' 转移过去就好,最后将lca(u',v')的贡献加进去即可
总结:1. 深搜 分块+预处理深度 2. 求lca主 3. sort 4.莫队转移得到answer
!!!!Trick:树上莫队按块排序 L, R的所在的块都要排序,因为在序列上(eg:数组)从小到大直接就是从近到远,但树上的序号却没有任何关系,因此必须要把L,R的块都排序
#include<cstdio> #include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<unordered_map> using namespace std; const int maxn = 4e5+7; int head[maxn],nxt[maxn*2],to[maxn*2],tot; int col[maxn],n,m,u,v,ans[maxn],Ans; int belong[maxn],block; int fa[maxn][32],d[maxn],f[maxn]; bool vis[maxn],vv[maxn]; int g[maxn]; unordered_map<int,int>rg; struct node{ int l,r,id; friend bool operator < (node a,node b){ if(belong[a.l] != belong[b.l]) return belong[a.l] < belong[b.l]; else return belong[a.r] < belong[b.r]; } }q[maxn]; void addedge(int u,int v) { to[++tot]=v; nxt[tot]=head[u]; head[u]=tot; } int s[maxn],cnt,sz[maxn],num; void dfs(int x) { vis[x]=1; s[++cnt]=x; for(int i=head[x];i;i=nxt[i]) { int v=to[i]; if(!vis[v]) { d[v]=d[x]+1; fa[v][0]=x; dfs(v); if(sz[x] + sz[v] >= block) { ++num; sz[x]=0; while(s[cnt]!=x) { belong[s[cnt]]=num; vv[s[cnt]]=1; --cnt; } } else sz[x]+=sz[v]; } } sz[x]++; } void init_rmq() { // fa[1][0]=1; for(int j=1;j<=30;j++) for(int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; } int LCA(int u,int v) { if(d[u]<d[v]) swap(u,v); int dc=d[u]-d[v]; for(int i=0;i<30;i++){ //è°�è�³å��ä¸�é«�度 if((1<<i)&dc) u=fa[u][i]; } if(u==v) return u; // ä¸�个ç�¹æ�°å¥½æ�¯å�¦ä¸�个ç�¹ç��lca for(int i=30;i>=0;i--){ //æ�¾å�°å�¯ä»¥è·³ç��æ��大ç��æ¥æ�°ï¼�使å¾�(u,v)ç��lcaä¸�å�� if(fa[u][i] != fa[v][i]) { u=fa[u][i]; v=fa[v][i]; } } return fa[u][0]; //uï¼�væ��å��ä¸�å®�å�¨lcaç��ä¸�é�¢ } void rev(int x) { if(!vis[x]){ f[col[x]]++; if(f[col[x]]==1) Ans++; vis[x]=1; } else{ f[col[x]]--; if(f[col[x]]==0) Ans--; vis[x]=0; } } void solve(int u,int v) { while(u!=v) { if(d[u]<d[v]){ rev(v),v=fa[v][0]; } else { rev(u),u=fa[u][0]; } } } int main() { scanf("%d%d", &n, &m); block=int(sqrt(n)); for(int i=1;i<=n;i++) { scanf("%d", &col[i]); g[i]=col[i]; } sort(g+1,g+n+1); int point=unique(g+1,g+n+1)-g-1; for(int i=1;i<=point;i++) rg[g[i]]=i; for(int i=1;i<=n;i++) col[i]=rg[col[i]]; for(int i=1;i<=n-1;i++) { scanf("%d%d", &u, &v); addedge(u,v),addedge(v,u); } // d[1]=1; dfs(1); ++num; for(int i=1;i<=n;i++){ if(!vv[i]) belong[i]=num; } init_rmq(); memset(vis,0,sizeof(vis)); for(int i=1;i<=m;i++){ scanf("%d%d", &u, &v); if(belong[u] > belong[v]) swap(u,v); q[i].l=u; q[i].r=v; q[i].id=i; } sort(q+1,q+m+1); int lca=LCA(q[1].l,q[1].r); solve(q[1].l,q[1].r); ans[q[1].id]=Ans+!(f[col[lca]]); for(int i=2;i<=m;i++) { lca=LCA(q[i].l,q[i].r); solve(q[i-1].l,q[i].l); solve(q[i-1].r,q[i].r); ans[q[i].id]=Ans+!(f[col[lca]]); } for(int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }