專題訓練之莫隊演算法
推薦部落格/專欄: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)BZOJ203815 { 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 }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;