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

我的并查集太蒻了

题意

给定一个n位的没有前导零的数,和m个限制(\(n,m \leq 1e5\)),每个限制给定\(l,r,L,R\),表示\([l,r]\)\([L,R]\)这些位上的数分别相同,求这个数有多少种,数据保证\(R-L==r-l\)

思路

询问多少种不同的数等价于求有多少位不受其它位的约束可以自由选择,那么\(ans=9*10^{cnt-1}\)

暴力显然就是对于每一个限制都直接枚举每一位,将它们放入并查集中,这样做时间复杂度是\(nm*\)玄学

考虑优化暴力,即不能枚举每一位,而是用一个区间表示一些位,首先容易想到用\(f(i,j)\)表示从i开始后面j位这一个区间,但是空间换时间,会爆炸,这时便可以使用倍增,使得空间时间两不误

\(f(i,j)\)表示从i开始,长为\(2^j\)的一个区间,对于限制区间\([l,r]\),将其二进制拆分,拆成log个后分别合并,便完成了合并

对于询问,之前可以直接find(i)==i?得到i是否不受约束,但是由于我们用区间表示一系列点,导致单点的信息缺失,不能直接查询一个点是否受约束,这时候就需要对区间进行拆分,找到\(f(i,j)\)对应的位置k,将两个区间都从中间分成两半,分别连一条边即可完成从\(f(i,j)\)\(f(i,j-1)\)\(f(i+(1<<(j-1)),j-1)\)的约束下放

Code:

#include<bits/stdc++.h>
#define N 100005
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
int n,m,fa[N][21];
template <class T>
void read(T &x)
{
    char c;int sign=1;
    while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
    while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
ll quickpow(ll a,ll b)
{
    ll ret=1;
    while(b)
    {
        if(b&1) ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}
int find(int x,int y) {return fa[x][y]==x ? fa[x][y] : fa[x][y]=find(fa[x][y],y);}
int main()
{
    read(n);read(m);
    for(int j=0;j<=20;++j)
        for(int i=1;i<=n;++i)
            fa[i][j]=i;
            
    for(int i=1;i<=m;++i)
    {
        int l,r,L,R;
        read(l);read(r);read(L);read(R);
        int len=(R-L+1),ll=l,LL=L;
        for(int j=20;j>=0;--j)
        {
            if(len>>j&1)
            {
                fa[find(fa[LL][j],j)][j]=find(fa[ll][j],j);
                ll+=(1<<j);
                LL+=(1<<j);
            }
        }
    }
    for(int j=20;j;--j)
    {
        for(int i=n+1-(1<<j);i>=1;--i)
        {
            int k=find(fa[i][j],j);
            fa[find(fa[i][j-1],j-1)][j-1]=find(fa[k][j-1],j-1);
            fa[find(fa[i+(1<<(j-1))][j-1],j-1)][j-1]=find(fa[k+(1<<(j-1))][j-1],j-1);
        }
    }
    int cnt=0;
    for(int i=1;i<=n;++i) if(find(i,0)==i) cnt++;
    cout<<9LL*quickpow(10,cnt-1)%mod<<endl;
    return 0;
}

转载于:https://www.cnblogs.com/Chtholly/p/11253085.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值