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);
}

本文介绍了解决BZOJ4569问题的方法,使用并查集和倍增技巧来合并区间。通过将区间拆分为不超过log个2的整数次幂长度的部分,并合并这些部分对应的点,最终统计最底层的连通块数量得出答案。

被折叠的 条评论
为什么被折叠?



