[SCOI2016]萌萌哒 解题报告
题意
有一个长度为 \(n\) 的大数,
有 \(m\) 个形如 l1,r1,l2,r2
的限制, 表示区间 \([l1,r1]\) 和 \([l2,r2]\) 完全相等,
求满足这些限制的数的个数, 不能含有前导零.
\((1 \le n,m \le 10^5)\)
思路
暴力 : 直接 \(O(nm)\) 把相等的数合成一个并查集, 最后若并查集的数量为 \(k\), 则答案为 \(9 \times 10^{k-1}\). (不能有前导零).
考虑优化.
首先想到线段树优化连边, 但是发现用线段树的话, 无法确定区间的对应关系.
为了连边之后依旧能确定区间的对应关系, 我们需要对每一个点都有一个以它为起点的存储结构.
所以, 我们可以弄一个类似于 ST表 的东西, 但理解方式类似于 线段树.
把整个序列分成 \(\log n\) 层, 每一层之间进行连边.
在给定了 l1,r1,l2,r2
时, 用类似于ST表查询最值时的操作, 把 \([l1,r1]\) 和 \([l2,r2]\) 分别分成两个长度为 \(2\) 的若干次方的区间, 然后在这几个区间之间连边.
在所有边都连完后, 从上往下一层一层地把连边下传, 反映在代码中其实就是并查集的合并.
需要注意的是, 在下传的时候, 设当前层的两个连接区间为 \(A,B\), 我们还需要考虑 \(A\) 和 \(B\) 的子区间, 即下一层的四个区间 \(A_1,A_2\) 和 \(B_1,B_2\) 的连接情况,
若 \(A_1,A_2\) 是相连的, 就把 \(fa[B_1],fa[B_2]\) 设为 \(fa[A_1]\),
否则, 就把 \(fa[A_1],fa[A_2]\) 设为 \(fa[B_1]\).
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=1e5+7;
const int L=20;
const ll mod=1e9+7;
int n,m,fa[_][L+7],Log[_];
bool vis[_];
ll ans;
void init(){
cin>>n>>m;
int t=0;
for(int i=1;i<=n;i++){
if(i==1<<(t+1)) t++;
Log[i]=t;
for(int k=0;k<=L;k++)
fa[i][k]=i;
}
}
int find(int i,int k){ return fa[i][k]==i ?i :fa[i][k]=find(fa[i][k],k); }
void run(){
int l1,r1,l2,r2;
int maxn=0;
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
maxn=max(maxn,max(r1,r2));
int t=Log[r1-l1+1];
int f1=find(l1,t),f2=find(l2,t);
fa[f1][t]=f2;
f1=find(r1-(1<<t)+1,t),f2=find(r2-(1<<t)+1,t);
fa[f1][t]=f2;
}
for(int k=L;k>=1;k--){
for(int i=1;i+(1<<k)-1<=n;i++){
int t=find(i,k),t1=find(i,k-1),t2=find(i+(1<<(k-1)),k-1);
if(t1==t2){ fa[find(t,k-1)][k-1]=fa[find(t+(1<<(k-1)),k-1)][k-1]=t1; }
else{ fa[t1][k-1]=find(t,k-1); fa[t2][k-1]=find(t+(1<<(k-1)),k-1); }
}
}
int j=find(1,0);
ans=9;
for(int i=2;i<=n;i++)
if(i!=j&&find(i,0)==i)
ans=ans*10%mod;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("x.in","r",stdin);
#endif
init();
run();
printf("%lld\n",ans);
return 0;
}