暑假D14 T2 monokuma(并查集+ST表)[SCOI2016]萌萌哒

题目描述

求一个长为n的数(不含前导零),使得它满足m个限制,每个限制为[l,r]位和[L,R]位对应相同。

对于100%的数据,1≤n,m≤100000,1≤Li≤Ri≤n,1≤li≤ri≤n,且保证Ri-Li=ri-li。

题解

可以想到如果给出两个区间,就可以在对应的数之间连一条边,只要一个确定了,与他连边的就确定了,最后看有多少联通块就相当于我们需要确定多少位置。

这不就是并查集的操作吗,不过显然暴力合并对应点会爆炸。

所以来了一种奇妙的做法,ST表+并查集。

首先按照ST表的套路划分出nlgn个块,把每个块看成一个点,并给出编号id[i][j]表示区间为i到i+2j-1的块。

在给出限制时,把区间分成按二进制分成小区间,因为两个区间长度相同,所以拆分相同。

对于每个拆出来的小区间进行合并。

最后再枚举每个块,如果他与其他块进行了合并,那么就将他的两个小区间也对应去合并。

最后看有多少长度为1的块fa是自己即可。

简直神仙。。。。。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1000005;
const int mod=998244353;
const ll inv9=443664157;
const ll inv10=299473306;
int fa[maxn*20],p[20];
int id[maxn][20],mp[maxn*20];//mp:编号为i的块开头的位置 
int n,m,num;

template<class T>inline void read(T &x){
    x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
}

int find(int x){
    if(x==fa[x]) return x;
    return fa[x]=find(fa[x]);
}

ll fpow(ll a,ll b){
    ll ret=1;
    while(b){
        if(b&1) ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}

void init(){
    p[0]=1;
    for(int i=1;i<=18;i++) p[i]=(p[i-1]<<1)%mod;
    for(int i=1;i<=n;i++)
     for(int j=0;j<=18&&i+p[j]-1<=n;j++)
      id[i][j]=++num,mp[num]=i;//给每个块一个编号 
    for(int i=1;i<=num;i++) fa[i]=i;
}

int main(){
    freopen("monokuma.in","r",stdin);
    freopen("monokuma.out","w",stdout);
    read(n);read(m);
    init();
    for(int i=1;i<=m;i++){
        int L,R,l,r;
        read(L);read(R);read(l);read(r);
        if(l==L) continue;
        for(int j=18;~j;j--)//将整个区间拆成小区间,分别合并 
         if(l+p[j]-1<=r){
             fa[find(id[l][j])]=fa[find(id[L][j])];
             l+=p[j];
             L+=p[j];
         }
    }
    for(int j=18;j;j--)
     for(int i=1;i+p[j]-1<=n;i++){
         int dx=find(id[i][j]),dy=mp[dx];//这个块的父亲,和父亲的开头位置 
         if(i==dy) continue;//这个区间没有改变
        fa[find(id[i][j-1])]=fa[find(id[dy][j-1])];
        fa[find(id[i+p[j-1]][j-1])]=fa[find(id[dy+p[j-1]][j-1])];//将两个小区间合并 
     }
    num=0;
    for(int i=1;i<=n;i++)
     if(find(id[i][0])==id[i][0]) num++;
    printf("%lld",inv9*fpow(inv10,num-1)%mod);
}
View Code

 

posted @ 2019-07-26 21:31  _JSQ  阅读(128)  评论(0编辑  收藏  举报