专题训练之莫队算法
推荐博客/专栏:https://blog.csdn.net/xianhaoming/article/details/52201761莫队算法讲解(含树上莫队)
https://blog.csdn.net/hzj1054689699/article/details/51866615莫队算法
https://zhuanlan.zhihu.com/p/25017840莫队算法
例题及讲解:(BZOJ2038)https://www.luogu.org/problemnew/show/P1494
讲解:https://www.cnblogs.com/MashiroSky/p/5914637.html
https://blog.csdn.net/xym_csdn/article/details/50889293
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 using namespace std; 6 typedef long long ll; 7 const ll maxn=5e4+10; 8 struct node{ 9 ll l,r,id,belong; 10 ll a,b; 11 }arr[maxn]; 12 ll num[maxn],a[maxn],ans; 13 14 bool cmp1(node a,node b) 15 { 16 if ( a.belong==b.belong ) return a.r<b.r; 17 return a.belong<b.belong; 18 } 19 20 bool cmp2(node a,node b) 21 { 22 return a.id<b.id; 23 } 24 25 ll gcd(ll x,ll y) 26 { 27 if ( y==0 ) return x; 28 return gcd(y,x%y); 29 } 30 31 void update(ll p,ll val) 32 { 33 ans-=num[a[p]]*num[a[p]]; 34 num[a[p]]+=val; 35 ans+=num[a[p]]*num[a[p]]; 36 } 37 38 int main() 39 { 40 ll n,m,i,j,k,x,y,z,l,r,sz; 41 while ( scanf("%lld%lld",&n,&m)!=EOF ) { 42 for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]); 43 sz=sqrt(n); 44 for ( i=1;i<=m;i++ ) { 45 scanf("%lld%lld",&arr[i].l,&arr[i].r); 46 arr[i].id=i; 47 arr[i].belong=(arr[i].l-1)/sz+1; 48 } 49 sort(arr+1,arr+1+m,cmp1); 50 l=1; 51 r=0; 52 ans=0; 53 memset(num,0,sizeof(num)); 54 for ( i=1;i<=m;i++ ) { 55 for ( ;r<arr[i].r;r++ ) update(r+1,1); 56 for ( ;r>arr[i].r;r-- ) update(r,-1); 57 for ( ;l>arr[i].l;l-- ) update(l-1,1); 58 for ( ;l<arr[i].l;l++ ) update(l,-1); 59 if ( arr[i].l==arr[i].r ) { 60 arr[i].a=0; 61 arr[i].b=1; 62 continue; 63 } 64 arr[i].a=ans-(arr[i].r-arr[i].l+1); 65 arr[i].b=(ll)(arr[i].r-arr[i].l+1)*(arr[i].r-arr[i].l); 66 k=gcd(arr[i].a,arr[i].b); 67 arr[i].a/=k; 68 arr[i].b/=k; 69 } 70 sort(arr+1,arr+1+m,cmp2); 71 for ( i=1;i<=m;i++ ) printf("%lld/%lld\n",arr[i].a,arr[i].b); 72 73 } 74 return 0; 75 }
练习题:
1.(HDOJ4858)http://acm.hdu.edu.cn/showproblem.php?pid=4858
分析:图的分块。设点i的点权为val[i],与点i相邻的项目的能量值之和为sum[i]。将图中的点分为重点和轻点,重点是那些边的度数超过sqrt(m)(该值可以自己规定)的点,除了重点剩下的点都是轻点。对于构图,重点只和重点建边,轻点可以和所有点建边。每次更新,对于重点i和轻点来说来说都是更新自己的val[i]和相邻点的sum[i]。而对于查询操作来说,重点直接输出sum[i],而轻点则采用暴力做法:遍历其每一个相邻点,答案累加上相邻的val[i]。采用的思想是分摊复杂度的思想
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 #include<cmath> 6 using namespace std; 7 typedef long long ll; 8 const ll maxn=1e5+100; 9 struct edge{ 10 ll u,v; 11 }arr[maxn]; 12 ll val[maxn],du[maxn],sum[maxn]; 13 bool ok[maxn]; 14 vector<ll>G[maxn]; 15 16 int main() 17 { 18 ll T,i,j,k,x,y,z,ans,cnt,n,m,sz,u,v,op,q; 19 scanf("%lld",&T); 20 while ( T-- ) { 21 scanf("%lld%lld",&n,&m); 22 for ( i=1;i<=n;i++ ) { 23 G[i].clear(); 24 ok[i]=false; 25 du[i]=val[i]=sum[i]=0; 26 } 27 for ( i=1;i<=m;i++ ) { 28 scanf("%lld%lld",&arr[i].u,&arr[i].v); 29 du[arr[i].u]++; 30 du[arr[i].v]++; 31 } 32 sz=sqrt(m); 33 for ( i=1;i<=n;i++ ) { 34 if ( du[i]>sz ) ok[i]=true; 35 } 36 for ( i=1;i<=m;i++ ) { 37 x=arr[i].u; 38 y=arr[i].v; 39 if ( ok[x] ) { 40 if ( ok[y] ) { 41 G[x].push_back(y); 42 G[y].push_back(x); 43 } 44 else { 45 G[y].push_back(x); 46 } 47 } 48 else { 49 if ( ok[y] ) { 50 G[x].push_back(y); 51 } 52 else { 53 G[x].push_back(y); 54 G[y].push_back(x); 55 } 56 } 57 } 58 scanf("%lld",&q); 59 while ( q-- ) { 60 scanf("%lld",&op); 61 if ( op==0 ) { 62 scanf("%lld%lld",&u,&x); 63 val[u]+=x; 64 for ( i=0;i<G[u].size();i++ ) { 65 v=G[u][i]; 66 sum[v]+=x; 67 } 68 } 69 else { 70 scanf("%lld",&u); 71 if ( ok[u] ) printf("%lld\n",sum[u]); 72 else { 73 ans=0; 74 for ( i=0;i<G[u].size();i++ ) { 75 v=G[u][i]; 76 ans+=val[v]; 77 } 78 printf("%lld\n",ans); 79 } 80 } 81 } 82 } 83 return 0; 84 }
2.(HDOJ4467)http://acm.hdu.edu.cn/showproblem.php?pid=4467
题意:给你n个点(每个点都有一个颜色,0代表黑色,1代表白色),m条边,每条边有一个权值.现在有有两个操作,一个是修改某个点的颜色(白变成黑/黑变成白),另外一个是询问那些边的两个端点都为指定颜色的权值总和
分析:采用上题相同的思想。将所有点分为重点和轻点,但是这次重点和重点之前的边要建在一个图中,剩余的边要建在另一个图中。对于最后访问的颜色,只有三种情况黑+黑(求和为0),黑+白(求和为1),白+白(求和为2),所以用a[0],a[1],a[2]分别对应的答案。对于重点i设置一个sum[i][2],sum[i][0]表示所有与他相邻且颜色为0(黑)的点的边权之和,sum[i][1]同理。更新时,对于重点i来说拿sum[i][0]和sum[i][1]去直接更新a数组,同时将其相邻的重点的sum值进行修改。而对于轻点i来说,遍历所有与i相连的边,暴力更新a数组,而当其相邻点为重点时则需要更新一下重点的sum数组。对于查询操作,直接输出a数组中的值即可
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 #include<cmath> 6 using namespace std; 7 typedef long long ll; 8 const ll maxn=1e5+10; 9 ll sum[maxn][2],a[5],color[maxn],du[maxn]; 10 bool ok[maxn]; 11 struct Edge{ 12 ll x,y,val; 13 }arr[maxn],arr_[maxn]; 14 struct edge{ 15 ll v,val; 16 edge(ll _v=0,ll _val=0):v(_v),val(_val) {} 17 }; 18 vector<edge>G[maxn],G_[maxn]; 19 20 bool cmp(Edge x,Edge y) 21 { 22 if ( x.x==y.x ) return x.y<y.y; 23 return x.x<y.x; 24 } 25 26 int main() 27 { 28 ll n,m,i,j,k,x,y,z,sz,cnt,q,ans,h=0; 29 char op[10]; 30 while ( scanf("%lld%lld",&n,&m)!=EOF ) { 31 for ( i=1;i<=n;i++ ) { 32 sum[i][0]=sum[i][1]=0; 33 ok[i]=false; 34 du[i]=0; 35 G[i].clear(); 36 G_[i].clear(); 37 } 38 memset(a,0,sizeof(a)); 39 for ( i=1;i<=n;i++ ) scanf("%lld",&color[i]); 40 for ( i=1;i<=m;i++ ) { 41 scanf("%lld%lld%lld",&x,&y,&arr[i].val); 42 if ( x>y ) swap(x,y); 43 arr[i].x=x; 44 arr[i].y=y; 45 a[color[x]+color[y]]+=arr[i].val; 46 } 47 sort(arr+1,arr+1+m,cmp); 48 cnt=0; 49 for ( i=1;i<=m;i=j ) { 50 for ( j=i+1;j<=m;j++ ) { 51 if ( arr[i].x==arr[j].x && arr[i].y==arr[j].y ) { 52 arr[i].val+=arr[j].val; 53 } 54 else break; 55 } 56 arr_[++cnt]=arr[i]; 57 } 58 sz=sqrt(cnt); 59 for ( i=1;i<=cnt;i++ ) { 60 du[arr_[i].x]++; 61 du[arr_[i].y]++; 62 } 63 for ( i=1;i<=n;i++ ) { 64 if ( du[i]>sz ) ok[i]=true; 65 } 66 for ( i=1;i<=cnt;i++ ) { 67 x=arr_[i].x; 68 y=arr_[i].y; 69 if ( ok[x] ) { 70 if ( ok[y] ) { 71 G_[x].push_back(edge(y,arr_[i].val)); 72 G_[y].push_back(edge(x,arr_[i].val)); 73 sum[x][color[y]]+=arr_[i].val; 74 sum[y][color[x]]+=arr_[i].val; 75 } 76 else { 77 G[y].push_back(edge(x,arr_[i].val)); 78 sum[x][color[y]]+=arr_[i].val; 79 } 80 } 81 else { 82 if ( ok[y] ) { 83 G[x].push_back(edge(y,arr_[i].val)); 84 sum[y][color[x]]+=arr_[i].val; 85 } 86 else { 87 G[x].push_back(edge(y,arr_[i].val)); 88 G[y].push_back(edge(x,arr_[i].val)); 89 } 90 } 91 } 92 printf("Case %lld:\n",++h); 93 scanf("%lld",&q); 94 while ( q-- ) { 95 scanf("%s",op); 96 if ( op[0]=='A' ) { 97 scanf("%lld%lld",&x,&y); 98 printf("%lld\n",a[x+y]); 99 } 100 else { 101 scanf("%lld",&x); 102 if ( ok[x] ) { 103 a[color[x]+0]-=sum[x][0]; 104 a[color[x]+1]-=sum[x][1]; 105 a[1-color[x]+0]+=sum[x][0]; 106 a[1-color[x]+1]+=sum[x][1]; 107 for ( i=0;i<G_[x].size();i++ ) { 108 y=G_[x][i].v; 109 z=G_[x][i].val; 110 sum[y][color[x]]-=z; 111 sum[y][1-color[x]]+=z; 112 } 113 } 114 else { 115 for ( i=0;i<G[x].size();i++ ) { 116 y=G[x][i].v; 117 z=G[x][i].val; 118 a[color[x]+color[y]]-=z; 119 a[1-color[x]+color[y]]+=z; 120 if ( ok[y] ) { 121 sum[y][color[x]]-=z; 122 sum[y][1-color[x]]+=z; 123 } 124 } 125 } 126 color[x]=1-color[x]; 127 } 128 } 129 } 130 return 0; 131 }
3.(HDOJ5213)http://acm.hdu.edu.cn/showproblem.php?pid=5213
题意:给我们n个数,然后给我们一个数字K,再给我们两个区间[L,R],[U,V],问我们从这两个区间里分别去两个数,有多少对取法,可以使得所取的两个数和为K。
分析:莫队算法+容斥定理。因为莫队算法是知道[l,r]的值后可以求[l',r']的值,因为每次都会给两个区间,所以我们需要将两个不相关的区间想办法让他们产生连续。对于[x1,y1][x2,y2]我们这时候可以通过容斥定理将其变为F[x1,y1,x2,y2]=F[x1,y2]+F[y1+1,x2-1]-F[y1+1,y2]-F[x1,x2-1]将一次求解变成4个区间分别求解,每次在一个区间中求解有多少种取法可以使得两个数和为k
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 using namespace std; 6 typedef long long ll; 7 const ll maxn=2e5+10; 8 struct edge{ 9 ll x1,x2,y1,y2; 10 ll ans; 11 }arr_[maxn]; 12 struct node{ 13 ll l,r,fa; 14 ll ans; 15 bool ok; 16 }arr[maxn]; 17 ll num[maxn],a[maxn],ans,belong[maxn],K; 18 19 bool cmp1(node a,node b) 20 { 21 if ( belong[a.l]==belong[b.l] ) return a.r<b.r; 22 return belong[a.l]<belong[b.l]; 23 } 24 25 void update(ll p,ll val) 26 { 27 num[a[p]]+=val; 28 if ( K>a[p] ) ans+=num[K-a[p]]*val; 29 } 30 31 int main() 32 { 33 ll n,m,i,j,k,x,y,z,l,r,sz,x1,x2,y1,y2; 34 bool flag; 35 while ( scanf("%lld",&n)!=EOF ) { 36 scanf("%lld",&K); 37 for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]); 38 sz=sqrt(n); 39 for ( i=1;i<=n;i++ ) belong[i]=(i-1)/sz+1; 40 scanf("%lld",&m); 41 for ( i=1;i<=m;i++ ) { 42 scanf("%lld%lld%lld%lld",&arr_[i].x1,&arr_[i].y1,&arr_[i].x2,&arr_[i].y2); 43 arr_[i].ans=0; 44 for ( j=0;j<4;j++ ) { 45 arr[i+j*m].fa=i; 46 arr[i+j*m].ans=0; 47 } 48 arr[i].ok=true;arr[i].l=arr_[i].x1;arr[i].r=arr_[i].y2; 49 arr[i+m].ok=true;arr[i+m].l=arr_[i].y1+1;arr[i+m].r=arr_[i].x2-1; 50 arr[i+m*2].ok=false;arr[i+m*2].l=arr_[i].x1;arr[i+m*2].r=arr_[i].x2-1; 51 arr[i+m*3].ok=false;arr[i+m*3].l=arr_[i].y1+1;arr[i+m*3].r=arr_[i].y2; 52 } 53 sort(arr+1,arr+1+m*4,cmp1); 54 l=1; 55 r=0; 56 ans=0; 57 memset(num,0,sizeof(num)); 58 for ( i=1;i<=4*m;i++ ) { 59 for ( ;r<arr[i].r;r++ ) update(r+1,1); 60 for ( ;r>arr[i].r;r-- ) update(r,-1); 61 for ( ;l>arr[i].l;l-- ) update(l-1,1); 62 for ( ;l<arr[i].l;l++ ) update(l,-1); 63 arr[i].ans=ans; 64 } 65 for ( i=1;i<=4*m;i++ ) { 66 x=arr[i].fa; 67 y=arr[i].ans; 68 flag=arr[i].ok; 69 if ( flag ) arr_[x].ans+=y; 70 else arr_[x].ans-=y; 71 } 72 for ( i=1;i<=m;i++ ) printf("%lld\n",arr_[i].ans); 73 74 } 75 return 0; 76 }
4.(HDOJ5145)http://acm.hdu.edu.cn/showproblem.php?pid=5145
题意:一个人有n个女朋友,每个女朋友都有一个班级a[i],现有m个询问。每个询问有一个区间范围[L,R],表示这个人想要约[L,R]范围内的女生有几种约法。
比如112 有112 121 211三种情况
123 有123 132 213 231 312 321六种情况
分析:给出公式,对于范围[L,R]来说可能的情况为(R-L+1)!/【(num[x1]!)*(num[x2]!)……*(num[xn]!)】 x1,x2,……,xn为[L,R]区间内出现过的所有不相同的数,num[]表示该数出现的次数
即情况数=该区间范围内所有数的全排列/每个数各自的全排列的求和。
因为出现取余操作,所以当出现除法操作时需要用到逆元(又因为mod=1e9+7为一个质数,所以考虑用费马小定理)
因为有很多阶乘操作,所以通过预处理得到可能范围内所以数的阶乘(保存在d[i]中)和阶乘的逆元(保存在nd[i]中)
因为答案的分子(即(R-L+1)!可在预处理后直接访问得到),所以在中间访问过程中只记录分母(记作ans)
当num[a[p]]]++时,需要ans*=num[a[p]];当num[a[p]]--时,需要ans/=num[a[p]];
可以将这个两个式子统一起来,当num[a[p]]发生改变时:1.ans*=nd[num[a[p]]] (本来是除,现在变成乘逆元) 2.num[a[p]]+=val (val可正可负) 3.ans*=d[num[a[p]]]
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 using namespace std; 6 typedef long long ll; 7 const ll maxn=3e4+10; 8 const ll N=3e4; 9 const ll mod=1000000007; 10 struct node{ 11 ll l,r,id,belong; 12 ll sum; 13 }arr[maxn]; 14 ll num[maxn],a[maxn],ans; 15 ll d[maxn],nd[maxn]; 16 17 bool cmp1(node a,node b) 18 { 19 if ( a.belong==b.belong ) return a.r<b.r; 20 return a.belong<b.belong; 21 } 22 23 bool cmp2(node a,node b) 24 { 25 return a.id<b.id; 26 } 27 28 ll quick(ll x,ll y) 29 { 30 ll sum=1; 31 while ( y ) { 32 if ( y&1 ) sum=(sum*x)%mod; 33 x=(x*x)%mod; 34 y/=2; 35 } 36 return sum%mod; 37 } 38 39 void init() 40 { 41 d[0]=nd[0]=1; 42 for ( ll i=1;i<=N;i++ ) d[i]=(d[i-1]*i)%mod; 43 for ( ll i=1;i<=N;i++ ) nd[i]=quick(d[i],mod-2); 44 } 45 46 void update(ll p,ll val) 47 { 48 ans=(ans*nd[num[a[p]]])%mod; 49 num[a[p]]+=val; 50 ans=(ans*d[num[a[p]]])%mod; 51 } 52 53 int main() 54 { 55 ll n,m,i,j,k,x,y,z,l,r,sz,T; 56 scanf("%lld",&T); 57 while ( T-- ) { 58 scanf("%lld%lld",&n,&m); 59 init(); 60 for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]); 61 sz=sqrt(n); 62 for ( i=1;i<=m;i++ ) { 63 scanf("%lld%lld",&arr[i].l,&arr[i].r); 64 arr[i].id=i; 65 arr[i].belong=(arr[i].l-1)/sz+1; 66 } 67 sort(arr+1,arr+1+m,cmp1); 68 l=1; 69 r=0; 70 ans=1; 71 memset(num,0,sizeof(num)); 72 for ( i=1;i<=m;i++ ) { 73 for ( ;r<arr[i].r;r++ ) update(r+1,1); 74 for ( ;r>arr[i].r;r-- ) update(r,-1); 75 for ( ;l>arr[i].l;l-- ) update(l-1,1); 76 for ( ;l<arr[i].l;l++ ) update(l,-1); 77 arr[i].sum=d[arr[i].r-arr[i].l+1]*quick(ans,mod-2)%mod; 78 } 79 sort(arr+1,arr+1+m,cmp2); 80 for ( i=1;i<=m;i++ ) printf("%lld\n",arr[i].sum); 81 82 } 83 return 0; 84 }
小结:对于莫队算法的题目,首先需要确定出在一段区间内所求答案的公式是什么(/怎么计算),再去考虑当范围改变时答案应该如何改变。有时候给出的不是一段区间,需要学会将所有问题转化到一段区间范围内的求解。