【BZOJ4569】[Scoi2016]萌萌哒 倍增+并查集

【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

Sample Output

90

题解:看到这种题比较直接的思路就是用并查集。对于每个限制,就将所有对应的位置所在并查集合并,设最后联通块个数为cnt,答案就是$9 \times 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;
}
posted @ 2017-07-16 09:17  CQzhangyu  阅读(701)  评论(0编辑  收藏  举报