0731-倍增+并查集-BZOJ4569-萌萌哒

传送门

4569: [Scoi2016]萌萌哒

Description

一个长度为n的大数,用S1S2S3...Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条

件表示为四个数,l1,r1,l2,r2,即两个长度相同的区间,表示子串Sl1Sl1+1Sl1+2...Sr1与Sl2Sl2+1Sl2+2...S

r2完全相同。比如n=6时,某限制条件l1=1,r1=3,l2=4,r2=6,那么123123,351351均满足条件,但是12012,13

1141不满足条件,前者数的长度不为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


算法分析

首先我们注意到数据范围是 1e5,要么是 O (n )的复杂度,要么是n log n等等

我们就可以从时间复杂度往算法推,然后不知怎么的,ldx就说到了倍增+并查集,然后就对了(Orz)。。。。。

我们用 fa [ i ] [ j ] 来表示 i 这个点往后 2 ^ j 个位置 这个区间的父亲(所谓父亲,就是与它相同的区间的最左端点)

然后我们的 j 是有 log(n )层的,我们从 log (n )这层开始往下传递,每次相当于折半下传,等传到最底层的时候,统计一下哪些位置的 fa 是它自己,那么就说明这个位置可以选择10个(除了一开头, 我们要排除前导0的情况,这个细节会在代码中解释),其他的位置就只有一种选择(就是和它祖先位置选的一样),然后乘在一起取个模就是答案啦~


代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#define mod 1000000007
using namespace std;
int n,m,l1,l2,r1,r2;
int fa[100009][25];
int find(int x,int j){	return x==fa[x][j]?x:fa[x][j]=find(fa[x][j],j);}
void merge(int x,int y,int j)
{
	int fx=find(x,j),fy=find(y,j);
	if(fx!=fy) fa[fx][j]=fy;
}
int main(){
	scanf("%d%d",&n,&m);
	int i,j,k;
	for(i=1;i<=n;++i)
		for(j=0;j<=20;++j)
			fa[i][j]=i;//初始化 
	for(i=1;i<=m;++i){
		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
		for(j=20;~j;--j)//~j   等价于   j!=-1 
			if(l1+(1<<j)<=1+r1)
				merge(l1,l2,j),l1+=(1<<j),l2+=(1<<j);//因为l1到r1的距离不一定刚好是2的某次幂 
	}
	for(i=20;i;--i)
		for(j=1;j+(1<<i)<=1+n;++j)
			merge(j,find(j,i),i-1),merge(j+(1<<i-1),fa[j][i]+(1<<i-1),i-1);//从最高层向下传 
	long long ans=9;int cnt=-1; //这行的初始值就和前导0有关,因为第一位的fa肯定是它自己 ,但只能取9个数 
	for(i=1;i<=n;++i)	if(i==find(i,0)) cnt++;
	while(cnt--) ans=ans*10%mod;
	printf("%lld",ans); 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/81300877