1. 程式人生 > >[BZOJ4569][SCOI2016]萌萌噠(倍增+並查集)

[BZOJ4569][SCOI2016]萌萌噠(倍增+並查集)

首先有一個顯然的$O(n^2)$暴力做法,將每個位置看成點,然後將所有限制相等的數之間用並查集合並,最後答案就是9*(10^連通塊的個數)。(特判n=1時就是10)。

然後比較容易想到的是,由於每次合併的是一個區間,逐個合併點過於浪費時間,考慮用線段樹建圖優化複雜度,但發現線段樹建圖並不能支援題目中的操作。

考慮常用來替代線段樹的ST表,對每個點i拆成log個,[j][i]表示i~i+(2^j)-1這段區間,我們稱它為i在第j層的點。

對於每個限制,將它拆成log個長度為2的次冪的區間,並分別在層內連邊。

所有限制處理完後,從第log(n)層(最高層)逐層下放資訊。若[j][a]與[j][b]有邊,那麼將[j-1][a]與[j-1][b]連邊,[j-1][a+(1<<(j-1))]與[j-1][b+(1<<(j-1))]連邊。當然若兩個同層點已經在同一個連通塊中則可以不用連邊。

這樣我們成功將邊數降到nlog級別,且第0層能完全記錄所有限制資訊,最後所求的答案就是9*(10^第0層的連通塊個數)。

若不考慮並查集複雜度,每個限制被拆成log個,每層中每個點只會下放兩條邊,共log層,故總複雜度$O((m+n)\log n)$。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 typedef long long ll;
 5 using namespace std;
 6 
 7
const int N=100010,mod=1e9+7; 8 int n,m,sm,ans=9,a,b,c,d,f[20][N],lg[N]; 9 10 int find(int k,int x){ return (f[k][x]==x) ? x : f[k][x]=find(k,f[k][x]); } 11 void uni(int k,int a,int b){ if (find(k,a)!=find(k,b)) f[k][f[k][a]]=f[k][b]; } 12 13 int main(){ 14 freopen("bzoj4569.in","r",stdin);
15 freopen("bzoj4569.out","w",stdout); 16 scanf("%d%d",&n,&m); 17 if (n==1){ puts("10"); return 0; } 18 rep(i,2,n) lg[i]=lg[i>>1]+1; 19 rep(j,0,lg[n]) rep(i,1,n-(1<<j)+1) f[j][i]=i; 20 rep(i,1,m){ 21 scanf("%d%d%d%d",&a,&b,&c,&d); 22 for (int j=lg[b-a+1]; ~j; j--) if (a+(1<<j)-1<=b) 23 uni(j,a,c),a+=1<<j,c+=1<<j; 24 } 25 for (int j=lg[n]; j; j--) rep(i,1,n-(1<<j)+1) 26 uni(j-1,i,find(j,i)),uni(j-1,i+(1<<(j-1)),f[j][i]+(1<<(j-1))); 27 rep(i,1,n) if (find(0,i)==i) sm++; 28 rep(i,2,sm) ans=ans*10ll%mod; 29 printf("%d\n",ans); 30 return 0; 31 }