P3295 [SCOI2016]萌萌哒
题目描述
一个长度为 nn 的大数,用 S_1S_2S_3 \cdots S_nS1S2S3⋯Sn表示,其中 S_iSi 表示数的第 ii 位, S_1S1 是数的最高位。告诉你一些限制条件,每个条件表示为四个数,l_1,r_1,l_2,r_2l1,r1,l2,r2,即两个长度相同的区间,表示子串S_{l_1}S_{l_1+1}S_{l_1+2} \cdots S_{r_1}Sl1Sl1+1Sl1+2⋯Sr1与S_{l_2}S_{l_2+1}S_{l_2+2} \cdots S_{r_2}Sl2Sl2+1Sl2+2⋯Sr2完全相同。
比如 n=6n=6 时,某限制条件 l_1=1,r_1=3,l_2=4,r_2=6l1=1,r1=3,l2=4,r2=6 ,那么 123123123123,351351351351 均满足条件,但是 1201212012,131141131141 不满足条件,前者数的长度不为 66 ,后者第二位与第五位不同。问满足以上所有条件的数有多少个。
输入格式
第一行两个数n和m,分别表示大数的长度,以及限制条件的个数。
接下来m行,对于第i行,有4个数 li1,ri1,li2,ri2,分别表示该限制条件对应的两个区间。
1\le n\le 10^51≤n≤105,1\le m\le 10^51≤m≤105 ,1\le li1,ri1,li2,ri2 \le n1≤li1,ri1,li2,ri2≤n ;并且保证 ri1-li1=ri2-li2ri1−li1=ri2−li2 。
输出格式
一个数,表示满足所有条件且长度为n的大数的个数,答案可能很大,因此输出答案模 10^9+7109+7 的结果即可。
输入输出样例
输入 #14 2 1 2 3 4 3 3 3 3输出 #1
90
自己写的时候脑抽了。。。。竟然以为是一个后缀数组的逆序问题。。。。。
我们想,对于一个位置,和他连接的必然值和他相同,这让我们想到了并查集。
那最后乘法原理得出的解就是9*10^(连通块的个数-1)(最高位为0的情况排除!)
然而数据范围给力。考虑优化。数据范围得知大致为O(nlogn),嗯想到辣倍增
处理f[i][j]为i---i+2^j-1的所属连通块,在每次限制的时候,倍增修改一下。
最后把大的推广到小的,即f[i][j]->f[i][j-1]和f[i+(1<<j)][j-1],
再查询一下连通块数量都可以辣!!!!真是妙极辣!!!!!
下面是代码:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 #define maxn 100010 6 #define mod 1000000007 7 #define ll long long 8 using namespace std; 9 int f[21][maxn],n,m,cnt;ll Ans=9; 10 int find(int x,int y){if(f[y][x]!=x)f[y][x]=find(f[y][x],y);return f[y][x];} 11 void merge(int x,int y,int len){ 12 if(find(x,len)!=find(y,len)) 13 f[len][f[len][x]]=f[len][y]; 14 } 15 int main(){ 16 //freopen("a.in","r",stdin); 17 scanf("%d%d",&n,&m);for(int j=0;j<=20;++j)for(int i=1;i<=n;++i)f[j][i]=i; 18 for(int i=1;i<=m;++i){ 19 int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d); 20 for(int j=20;j!=-1;j--) 21 if(a+(1<<j)-1<=b)merge(a,c,j),a+=1<<j,c+=1<<j; 22 } 23 for(int j=20;j;j--) 24 for(int i=1;i+(1<<j)-1<=n;++i) 25 merge(i,find(i,j),j-1), 26 merge(i+(1<<(j-1)),f[j][i]+(1<<(j-1)),j-1); 27 for(int i=1;i<=n;++i)if(find(i,0)==i)cnt++; 28 for(int i=1;i<cnt;++i)Ans*=10,Ans%=mod;cout<<Ans; 29 return 0; 30 }