[SCOI2016]萌萌哒(倍增+并查集)

一个长度为n的大数,用S1S2S3...Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条件表示为四个数,l1,r1,l2,r2,即两个长度相同的区间,表示子串Sl1Sl1+1Sl1+2...Sr1与Sl2Sl2+1Sl2+2...Sr2完全相同。比如n=6时,某限制条件l1=1,r1=3,l2=4,r2=6,那么123123,351351均满足条件,但是12012,131141不满足条件,前者数的长度不为6,后者第二位与第五位不同。问满足以上所有条件的数有多少个。

Solution

涨姿势了。

不难想到用并查集维护数字之间的相等关系,最后用联通块个数统计答案。

但这样的复杂度是n^2的,需要去优化它,

考虑到每次合并都是两段等长的区间进行合并,所以我们考虑使用倍增。

我们开nlogn个并查集,num[i][j]表示从i开始的2^j个数,每次区间合并我们把它拆成logn个区间分别合并。

最后自顶向底合并儿子,就像线段树一样,

Code

#include<iostream>
#include<cstdio>
#define N 100002
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int num[N][20],f[N*20],n,m,tot,son[N*20][2],l1,r1,l2,r2;
int find(int x){return f[x]=f[x]==x?x:find(f[x]);}
long long power(ll x,int y){
    ll ans=1;
    while(y){
        if(y&1)(ans*=x)%=mod;
        (x*=x)%=mod;
        y>>=1;
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;(1<<i)<=n;++i)
      for(int j=1;j+(1<<i)-1<=n;++j){
        num[j][i]=++tot;f[tot]=tot;
        if(i){
        son[tot][0]=num[j][i-1];
        son[tot][1]=num[j+(1<<i-1)][i-1];
        }
       }
    for(int i=1;i<=m;++i){
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        int len=r1-l1+1;
        for(int j=19;j>=0;--j)
         if((1<<j)<=len){
               int x=find(num[l1][j]),y=find(num[l2][j]);
               if(x!=y)f[x]=y; 
               len-=(1<<j);l1+=(1<<j);l2+=(1<<j);
         }
    } 
    for(int i=19;i>=0;--i)
      for(int j=1;j+(1<<i)-1<=n;++j){
          int root=num[j][i];
          if(find(root)!=root){
             int x=find(son[root][0]),y=find(son[f[root]][0]);
           if(x!=y)f[x]=y;    
           x=find(son[root][1]),y=find(son[f[root]][1]);
           if(x!=y)f[x]=y;
        }
    }
    int ans=0;
    for(int i=1;i<=n;++i)if(find(num[i][0])==num[i][0])ans++;   
    printf("%lld",9*power(10,ans-1)%mod);
    return 0;
}

 

posted @ 2018-10-19 15:31  comld  阅读(171)  评论(0编辑  收藏  举报