题目传送门:【BZOJ 4569】
题目大意: 有一个长度为 n 的大数。告诉你一些限制条件,每个条件表示为四个数,L1,R1,L2,R2,即两个长度相同的区间,表示区间内对应的数相同。比如 n=6 时,某限制条件 L1=1,R1=3,L2=4,R2=6,那么 123123,351351 这两组数均满足条件,但是 12012,131141 这两组数不满足条件,前者数的长度不为 6,后者第二位与第五位不同。现在,请你求出,满足以上所有条件的数有多少个。
题目分析:
在分析之前,先 Orz gengchen的题解 给我带来的启发
(这道题他讲得更详细,代码也更好懂)
进入正题
由题,首先看到这道题,由于题目性质为“对应的数位相等”,并且当数位之间有重合的时候,就会有对应的每个数都相等的情况(其实这样说是不准确的,但是,这些数至少都是相同的几组数),所以我们可以考虑使用并查集做法。
但是,朴素的并查集在这里会造成极大的浪费,很多情况被重复计算。所以,我们需要更好地优化并查集。在这里,我们可以使用最优的 ST 表优化方式(即:利用倍增的思想,对每个区块的数进行优化)。
具体的操作,可以看下面的代码,或者去上面看更详细的题解:
- #include<cstdio>
- #include<algorithm>
- typedef long long LL;
- const int MX=100005;
- const int MXLOG=20;
- const int MOD=998244353;
- int n,m,tot=0,ans=1;
- int f[MXLOG][MX],log[MX*MXLOG],fa[MX*MXLOG],left[MX*MXLOG],right[MX*MXLOG];
- //为了使用倍增而多开了log(n)的空间
- LL fastpow(int a,int b,int mod){
- LL r=1,base=a;
- while (b){
- if (b&1) r=r*base%mod;
- base=base*base%mod;
- b>>=1;
- }
- return r%mod;
- }
- void preprocess(int n){
- for (int i=1,j=0;i<=n;i<<=1,j++)log[i]=j;
- for (int i=1;i<=n;i++)log[i]=std::max(log[i-1],log[i]);
- for (int i=0;i<=log[n];i++){
- for (int j=1;j+(1<<i)-1<=n;j++){
- f[i][j]=++tot;
- if (i>0){
- //运用了ST表中的倍增思想
- left[tot]=f[i-1][j];
- right[tot]=f[i-1][j+(1<<(i-1))];
- }
- }
- }
- }
- inline int find(int x){
- if (fa[x]==x) return x;
- return fa[x]=find(fa[x]);
- }
- void merge(int lf,int rt){
- int x=find(lf),y=find(rt);
- if (x!=y) fa[x]=y;
- }
- int main(){
- int Li,Ri,li,ri;
- scanf(”%d%d”,&n,&m);
- if (n==1){
- printf(”10”);
- return 0;
- }
- preprocess(n);
- for (int i=1;i<=tot;i++) fa[i]=i;
- for (int i=1;i<=m;i++){
- scanf(”%d%d%d%d”,&Li,&Ri,&li,&ri);
- if (Li==li) continue;
- int size=log[Ri-Li+1];
- merge(f[size][Li],f[size][li]);
- merge(f[size][Ri-(1<<size)+1],f[size][ri-(1<<size)+1]);
- }
- for (int i=tot;i>n;i–){
- int j=find(i);
- if (i!=j){
- //对应位置合并
- merge(left[i],left[j]);
- merge(right[i],right[j]);
- }
- }
- for (int i=1;i<=n;i++)
- if (find(i)==i) ans=10LL*ans%MOD;
- printf(”%lld”,9LL*ans%MOD*fastpow(10,MOD-2,MOD)%MOD);
- return 0;
- }