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