P3295 SCOI2016 萌萌哒

P3295 SCOI2016 萌萌哒

有趣的并查集+倍增 trick。

思路

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

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

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

用倍增来连接一长段,将区间 [l1,r1][l2,r2] 分为 log(rl) 段,对应每段之间都用并查集连接,一共连接 log(rl) 次,表示的含义为这两段完全相同。

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

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

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 @   彬彬冰激凌  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示