[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
",ans);
30     return 0;
31 }
原文地址:https://www.cnblogs.com/HocRiser/p/10088434.html