专题训练之莫队算法

推荐博客/专栏: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 }
BZOJ2038

 

 

练习题:

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 }
HDOJ4858

 

 

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 }
HDOJ4467

 

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 }
HDOJ5213

 

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 }
HDOJ5145

 

 

小结:对于莫队算法的题目,首先需要确定出在一段区间内所求答案的公式是什么(/怎么计算),再去考虑当范围改变时答案应该如何改变。有时候给出的不是一段区间,需要学会将所有问题转化到一段区间范围内的求解。

posted @ 2018-05-07 19:58  HDU_jackyan  阅读(1042)  评论(1编辑  收藏  举报