题目
有一个长度为(n)的十进制数,用(s)表示。有(m)个限制条件,每个条件形如:((l_1,r_1,l_2,r_2)),表示(s[l_1:r_1]=s[l_2:r_2])。
现在给出这些限制条件,问有多少个数满足条件。
(n,mle 10^5) 。
分析
这个题这是神奇!!
首先如果暴力的话,那么我们是把每一个条件的每一个对应位用并查集并起来,最后统计集合的个数(x),就可以用(9*10^{x-1})来计算答案了(第一位所在的集合只能填1-9)。
然而限制条件数特别多,暴力显然是不行的。最开始想的是线段树,然而不会做。
考虑类似ST表的方法,我们把这个区间划分成前(2^j)位和后(2^j)位,那么就变成了这两端(2^j)位分别对应相同。我们开(log n)个并查集,每一层记录对应(j)的相同性,我们就可以快速处理完每个条件了。
处理完所有条件之后,我们会得到(log n)个并查集,第(j)个并查集的(x,y)在同一个集合中就表示(s[x:x+2^j-1]=s[y:y+2^j-1])。我们从上往下((j)从大到小)把这个相同性推到下一层去,就可以在总时间(O((n+m)log ncdot alpha (n)))的复杂度内得到最后的并查集。最后扫一遍得到集合数即可。
这题中先用ST表思想处理条件,最后统计的方法是很妙的。
代码
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long giant;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxn=1e5+1;
const int maxj=17;
const int q=1e9+7;
int bin[maxn],n;
inline int Multi(int x,int y) {return (giant)x*y%q;}
inline int mi(int x,int y) {
int ret=1;
for (;y;y>>=1,x=Multi(x,x)) if (y&1) ret=Multi(ret,x);
return ret;
}
struct SET {
int f[maxn];
void init(int n) {for (int i=1;i<=n;++i) f[i]=i;}
int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
int merge(int x,int y) {
int fx=find(x),fy=find(y);
if (fx!=fy) f[fx]=fy;
}
} st[maxj];
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
n=read();
for (int i=2;i<=n;++i) bin[i]=bin[i>>1]+1;
for (int i=bin[n];i>=0;--i) st[i].init(n);
for (int m=read();m;--m) {
int x=read(),y=read(),l=read(),r=read(),len=r-l+1,d=bin[len];
st[d].merge(x,l);
st[d].merge(y-(1<<d)+1,r-(1<<d)+1);
}
for (int j=bin[n];j;--j) {
for (int i=1;i+(1<<j)-1<=n;++i) {
int p=st[j].find(i);
st[j-1].merge(i,p);
st[j-1].merge(i+(1<<(j-1)),p+(1<<(j-1)));
}
}
int cnt=0;
for (int i=1;i<=n;++i) cnt+=(st[0].find(i)==i);
int ans=Multi(9,mi(10,cnt-1));
printf("%d
",ans);
return 0;
}