P3295 SCOI2016 萌萌哒

P3295 SCOI2016 萌萌哒

有趣的并查集+倍增 trick。

思路

考虑将必须填同一个数的位置连一条边,最后若出现了 \(k\) 个连通块,那答案就是 \(9\times 10^{k-1}\)(首位不为 \(0\))。

我们用并查集暴力连接,时间复杂度 \(O(nm)\),显然是无法接受的。

有没有办法一次性可以连很多块呢?

用倍增来连接一长段,将区间 \([l_1,r_1]\)\([l_2,r_2]\) 分为 \(\log (r-l)\) 段,对应每段之间都用并查集连接,一共连接 \(\log (r-l)\) 次,表示的含义为这两段完全相同。

由于查询需要落在点上,设 \(f(p,k)\)\(p\) 向后 \(2^k\) 并查集的根,我们将段 \((f(p,k),k-1)\)\((p,k-1)\) 连接、\((f(p,k)+2^{k-1},k-1)\)\((p+2^{k-1},k-1)\) 连接,这样就将一段下传到点上。

最后统计连通块个数即可。

CODE

#include<bits/stdc++.h>
using namespace std;

#define ll long long

const ll mod=1e9+7;
const int maxn=1e5+5;

int n,m;
int lg[maxn];

struct DSU
{
    int f[maxn];
    inline void init(int n){for(int i=1;i<=n;i++) f[i]=i;}
    inline int fr(int u){return u==f[u]?u:f[u]=fr(f[u]);}
    inline void merge(int u,int v){u=fr(u),v=fr(v);if(u==v) return ;f[u]=v;}
}F[20];

bool vis[maxn];

inline ll ksm(ll x,ll y)
{
    ll sum=1;
    for(;y;y/=2,x=x*x%mod) if(y&1) sum=sum*x%mod;
    return sum;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
    for(int i=0;i<=lg[n];i++) F[i].init(n);
    for(int i=1;i<=m;i++)
    {
        int l1,r1,l2,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        int len=r1-l1+1;
        for(int k=0;len;len>>=1,k++)
        {
            if(len&1)
            {
                int p1=l1+((len>>1)<<(k+1)),p2=p1+(l2-l1);
                F[k].merge(p1,p2);
            }
        }
    }
    for(int i=lg[n];i;i--)
    {
        for(int j=1;j<=n;j++)
        {
            int f=F[i].fr(j);
            F[i-1].merge(f,j);
            F[i-1].merge(f+(1<<(i-1)),j+(1<<(i-1)));
        }
    }
    int num=0;
    for(int i=1;i<=n;i++) if(!vis[F[0].fr(i)]) vis[F[0].fr(i)]=1,num++;
    printf("%lld\n",9*ksm(10,num-1)%mod);
}
posted @ 2025-02-04 15:11  彬彬冰激凌  阅读(30)  评论(0)    收藏  举报