BZOJ4569: [Scoi2016]萌萌哒
并查集·倍增
题解:
并查集中点
id[i][j]
表示从i开始
2j
长度的这一块区间。
合并的时候区间拆成不超过log个2的整数次幂长度的区间,把他们对应的点合并。
最后自顶向下合并,即如果
id[i][j]]
和
id[a][b]
在一个并查集里,则合并
id[i][j−1]、id[a][b−1]
以及
id[i+2j−1][j−1]、id[a+2b−1][b−1]
.
答案数一数最底层有几个连通块就可以算出来。
Code:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 100005;
const long long mod = 1000000007;
const int LOG = 20;
int n,m,tot;
int pa[N*21],base[N*21],log[N*21],pow[21],id[N][21];
int find(int x){ return pa[x]?pa[x]=find(pa[x]):x; }
void un(int x,int y){
int xx=find(x), yy=find(y);
if(xx!=yy) pa[xx]=yy;
}
void init(){
for(int i=0;i<=LOG;i++) pow[i]=1<<i;
for(int i=1;i<=n;i++){
for(int j=0;j<=LOG;j++){
id[i][j]=++tot;
base[tot]=i; log[tot]=j;
}
}
}
int main(){
freopen("a.in","r",stdin);
scanf("%d%d",&n,&m);
init();
while(m--){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
for(int j=LOG;j>=0;j--){
if(a+pow[j]-1 <= b){
un(id[a][j],id[c][j]);
a+=pow[j]; c+=pow[j];
}
}
}
for(int j=LOG;j>0;j--){
for(int i=1;i<=n;i++){
if(i+pow[j]-1 > n) break;
int x=find(id[i][j]);
int a=base[x], b=log[x];
un(id[a][b-1],id[i][j-1]);
un(id[a+pow[b-1]][b-1],id[i+pow[j-1]][j-1]);
}
}
long long ans=9; bool flag=false;
for(int i=1;i<=n;i++){
if(!pa[id[i][0]]){
if(flag) ans=ans*10%mod;
flag=true;
}
}
printf("%lld\n",ans);
}