分块、莫队算法
来源:互联网 发布:java脱产班 编辑:程序博客网 时间:2024/05/16 08:58
首先,是利用分块的思想处理区间问题
比如这个:(D-query)点击打开链接
Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, ..., aj.
Input
- Line 1: n (1 ≤ n ≤ 30000).
- Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).
- Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
- In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).
Output
- For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.
Example
Input51 1 2 1 331 52 43 5Output323题目大意是要求一段区间内不同元素的个数
首先,暴力分析,n*m,TLE
然后,考虑优化,如何尽可能的利用已经求过的值,这是莫队算法的核心,也是近乎所有优化的核心
先考虑这样一个问题:如果我们已经知道了区间[L,R]之间的内容(此处即为数字的个数,记为cnt[number]==个数),并且已经知道[L,R]之间不同元素的个数(此处记为sum)。
那么当我们要求[L-1,R]或是[L,R+1]时,就可以以O(1)的时间得到更新的cnt,并通过cnt的变化(此处表现为cnt[num]刚好加到一次或刚好减为0)来得到新的sum值
相信这个思想大家曾经都想过,但如何使L和R移动的次数尽可能少而覆盖所有的询问,是莫队所要解决的。
自然而然的,我们想到按照L从小到大将询问排序,但当L变化时,R仍然可能从L取到n,时间仍很大。
于是我们将L分块,(从现在起,记l为数组下标,L为该下标所在块,r和R同理)
我们将询问以L为第一关键字,R为第二关键字排序,
那么,询问的区间就会变成这样:
[L,R],[L,R],[L,R+1],[L,R+k],[L,n],
[L+1,R+2],[L+1,R+2],[L+1,R+k],
[L+k,R+p].......
此时再启用暴力修改,可以发现:
在同一块内,l和r的波动范围(记每块长度为k)就是k,时间复杂度降为了((n/k)*(n/k)*k),取k=sqrt(n)时为O(n*sqrt(n)).
通过巧妙的阈值,莫队通过L的波动平衡了R变化的O(n*n)与L变化的O(n),是指总复杂度变为O(n*sqrt(n)).
代码如下:
#include<cstdio>#include<algorithm>#include<cmath>using namespace std;int n,m,k,a[30005],cnt[1000005],x,y,sum,ans[200005];//注意cnt的大小,不然容易REstruct node{int l,r,L,R,id;bool operator < (const node &p)const{if(L==p.L) return R<p.R;else return L<p.L;}}q[200005];int main(){scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);k=sqrt(n);scanf("%d",&m);for(int i=1;i<=m;i++)scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i,q[i].L=q[i].l/k,q[i].R=q[i].r/k;sort(q+1,q+1+m);//离线处理询问x=1,y=sum=0;//设置初始区间for(int i=1;i<=m;i++){while(x>q[i].l) sum+=(++cnt[a[--x]]==1);//修改范围,拓展或收缩while(y<q[i].r) sum+=(++cnt[a[++y]]==1);while(x<q[i].l) sum-=(--cnt[a[x++]]==0);while(y>q[i].r) sum-=(--cnt[a[y--]]==0);ans[q[i].id]=sum;}for(int i=1;i<=m;i++)printf("%d\n",ans[i]);}
另外,莫队的变式通常会在sum值的统计上作文章。
比如Powerful array 点击打开链接
sum的求法变成了这样:
while(x>q[i].l) sum+=(2*cnt[a[x-1]]+1)*a[x-1],cnt[a[--x]]++;while(y<q[i].r) sum+=(2*cnt[a[y+1]]+1)*a[y+1],cnt[a[++y]]++;while(x<q[i].l) sum-=(2*cnt[a[x]]-1)*a[x],cnt[a[x++]]--;while(y>q[i].r) sum-=(2*cnt[a[y]]-1)*a[y],cnt[a[y--]]--;
还要注意莫队的一个坑:x,y的拓展顺序,先拓展、在收缩,不然可能会出现x调整时到了还未来得及调整的y的右边,导致cnt为负数,在某些情况下并不影响答案(如上面两题),但某些情况则会(如下):
while(y<q[i].r) sum=sum*inv[++cnt[a[++y]]]%mod;//attention!!while(x>q[i].l) sum=sum*inv[++cnt[a[--x]]]%mod;//first go out.avoid -number.while(x<q[i].l) sum=sum*cnt[a[x++]]--%mod;while(y>q[i].r) sum=sum*cnt[a[y--]]--%mod;此处若sum的求法如上,则cnt为负时数组访问便会越界。(NPY and girls 点击打开链接)
还有,莫队的变式有时会涉及到前缀数组,要把原数组进行一些处理:
XOR and Favorite Number 点击打开链接
该题需要求的是一个区间异或的“和”,注意,这种前缀题,区间的左端点要往前取一位(相减才能把整个区间取完)
修改大致如下:
scanf("%d",&a[i]),a[i]^=a[i-1];
scanf("%d%d",&q[i].l,&q[i].r),q[i].l--,q[i].id=i,q[i].L=q[i].l/k,q[i].R=q[i].r/k;
while(x>q[i].l) sum+=cnt[p^a[x-1]],cnt[a[--x]]++;while(y<q[i].r) sum+=cnt[p^a[y+1]],cnt[a[++y]]++;while(x<q[i].l) cnt[a[x]]--,sum-=cnt[p^a[x++]];while(y>q[i].r) cnt[a[y]]--,sum-=cnt[p^a[y--]];
最后,是莫队的修改操作。将第几次修改作为第三关键字排序,记录每个修改操作施加的位置、以及修改前和修改后的值,进行暴力修改和还原,记区间长为k,则当L和R一定时,t从1取到n,O((n/k)^2*n),R波动n*k,L波动n*k,当k取n的三分之二次方时总复杂度最小(通常可以直接令k=100~300)
PS:注意两个修改操作如果施加在同一点,则第二次操作的原值应为第一次修改后的值(所以最好开两个数组存原序列)。
数颜色
点击打开链接struct node{int l,r,L,R,t,id;bool operator < (const node &p)const{if(L==p.L){if(R==p.R) return t<p.t;else return R<p.R;}else return L<p.L;}}q[maxn];
if(c[0]=='Q'){int id=i-tim;scanf("%d%d",&q[id].l,&q[id].r);q[id].id=id;q[id].t=tim;q[id].L=q[id].l/k,q[id].R=q[id].r/k;}else {tim++;scanf("%d%d",&turn[tim].pos,&turn[tim].cl2);turn[tim].cl1=b[turn[tim].pos];b[turn[tim].pos]=turn[tim].cl2;}
while(t<q[i].t){t++;a[turn[t].pos]=turn[t].cl2;if(x<=turn[t].pos&&turn[t].pos<=y){sum-=(--cnt[turn[t].cl1]==0);sum+=(++cnt[turn[t].cl2]==1);}}while(t>q[i].t){a[turn[t].pos]=turn[t].cl1;if(x<=turn[t].pos&&turn[t].pos<=y){sum-=(--cnt[turn[t].cl2]==0);sum+=(++cnt[turn[t].cl1]==1);}t--;}
- 分块、莫队算法
- 分块_莫队算法
- 关于分块算法and莫队算法
- BZOJ 3585 mex 莫队算法+分块
- 莫队算法 sqrt(n)分块思想
- 莫队分块算法模板[BZOJ2038]
- 【bzoj3585】mex 分块+莫队算法
- bzoj3585 mex 分块+莫队算法
- BZOJ 3585 mex 莫队算法+分块
- BZOJ 3585 mex 莫队算法+分块
- WHU-1551-Pairs(莫队算法+分块实现)
- NBUT 1457 Sona 莫队算法 分块处理
- hdu5145 NPY and girls 莫队算法,分块
- hdu 4638 Group(莫队算法+分块)
- jzoj 3547. 【清华集训2014】mex 分块+莫队算法
- bzoj 3781: 小B的询问 莫队算法+分块
- bzoj 3236: [Ahoi2013]作业 莫队算法+分块
- 【BZOJ2038】小Z的袜子(hose)(莫队算法 + 分块)
- Fragment
- 第二周学习笔记
- 移动平台的证件扫描识别技术
- flask中的session cookie 测试 和 项目中的用户状态保持
- [MySQL必知必会]创建计算字段
- 分块、莫队算法
- python中的进程
- Linux查看登录用户日志
- 第2章 Nginx服务器的安装部署
- Spring基础之Bean的生命周期
- 异步Socket通信编程的C#实现(1)
- scikit-learn中随机森林使用详解
- 枫林红叶随身落, 遍地花海迹中寻。 独贪长不愿醒, 回首百年烟云过。
- JAVA生成验证码