题目描述
求一个长为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); }