【BZOJ4569】[Scoi2016]萌萌哒
Description
一个长度为n的大数,用S1S2S3...Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条件表示为四个数,l1,r1,l2,r2,即两个长度相同的区间,表示子串Sl1Sl1+1Sl1+2...Sr1与Sl2Sl2+1Sl2+2...Sr2完全相同。比如n=6时,某限制条件l1=1,r1=3,l2=4,r2=6,那么123123,351351均满足条件,但是12012,131141不满足条件,前者数的长度不为6,后者第二位与第五位不同。问满足以上所有条件的数有多少个。
Input
第一行两个数n和m,分别表示大数的长度,以及限制条件的个数。接下来m行,对于第i行,有4个数li1,ri1,li2,ri2,分别表示该限制条件对应的两个区间。1≤n≤10^5,1≤m≤10^5,1≤li1,ri1,li2,ri2≤n;并且保证ri1-li1=ri2-li2。
Output
一个数,表示满足所有条件且长度为n的大数的个数,答案可能很大,因此输出答案模10^9+7的结果即可。
Sample Input
4 2
1 2 3 4
3 3 3 3
1 2 3 4
3 3 3 3
Sample Output
90
题解:看到这种题比较直接的思路就是用并查集。对于每个限制,就将所有对应的位置所在并查集合并,设最后联通块个数为cnt,答案就是$9 imes 10^{cnt-1}$。
然后比较直接的想法就是用线段树优化这个过程,不过想了半天,线段树好像。。。做不到啊。
看题解发现是倍增,如何实现呢?每次限制,我们相当于将log对区间的并查集合并(每对区间的大小相等)。最后也像线段树那样,pushdown一下。即如果两个区间在同一个并查集中,那么他们的左半部分和右半部分都分别在相同的并查集中。
#include <cstdio> #include <cstring> #include <iostream> #define P(A,B) ((A)*n+(B)) using namespace std; const int maxn=100010; const long long mod=1000000007; int n,m,sum; long long ans; int f[20][maxn],Log[maxn]; int find(int x,int y) { return (f[y][x]==x)?x:(f[y][x]=find(f[y][x],y)); } int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-')f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar(); return ret*f; } void updata(int a,int b,int c) { if(find(a,c)!=find(b,c)) f[c][f[c][a]]=f[c][b]; } int main() { n=rd(),m=rd(); int i,j,a,b,c,d; for(i=2;i<=n;i++) Log[i]=Log[i>>1]+1; for(j=0;(1<<j)<=n;j++) for(i=1;i+(1<<j)-1<=n;i++) f[j][i]=i; for(i=1;i<=m;i++) { a=rd(),b=rd(),c=rd(),d=rd(); for(j=Log[b-a+1];j>=0;j--) if(a+(1<<j)-1<=b) updata(a,c,j),a+=(1<<j),c+=(1<<j); } for(j=Log[n];j;j--) { for(i=1;i+(1<<j)-1<=n;i++) updata(i,find(i,j),j-1),updata(i+(1<<j-1),f[j][i]+(1<<j-1),j-1); } for(i=1;i<=n;i++) if(find(i,0)==i) sum++; for(ans=9,i=1;i<sum;i++) ans=ans*10%mod; printf("%lld",ans); return 0; }