1. 程式人生 > >專題訓練之莫隊演算法

專題訓練之莫隊演算法

推薦部落格/專欄: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;