【题目】
题目传送门萌萌哒
题目描述
一个长度为 n 的大数,用 … 表示,其中 表示数的第 位, 是数的最高位,告诉你一些限制条件,每个条件表示为四个数,,,, ,即两个长度相同的区间,表示子串... 与 ... 完全相同。
比如 n=6 时,某限制条件 =1,=3,=4,=6,那么 123123,351351 均满足条件,但是 12012,131141 不满足条件,前者数的长度不为 6,后者第二位与第五位不同。
问满足以上所有条件的数有多少个。
输入格式
第一行两个数 n 和 m ,分别表示大数的长度,以及限制条件的个数。
接下来 m 行,对于第 行, 有 4 个数 , , , ,分别表示该限制条件对应的两个区间。
输出格式
一个数,表示满足所有条件且长度为 n 的大数的个数,答案可能很大,因此输出答案模 的结果即可。
样例数据
输入
4 2
1 2 3 4
3 3 3 3
输出
90
备注
【数据范围】
对于 30% 的数据,1 ≤ n ≤ 2000,1 ≤ m ≤ 2000;
对于 100% 的数据,1 ≤ n ≤ ,1 ≤ m ≤ ,1 ≤ , , , ≤ n;
并且保证 - = - 。
【分析】
所以说这道题跟萌萌哒有什么关系吗
不过这道题经dzy大佬讲解之后依旧不是很懂,果然我还是太蒟蒻了吗
30分做法:我们将每次限制的 和 放入同一个集合, 和 放入同一个集合…… 和 放入同一个集合,在同一个集合中的元素代表在这些位置上我们必须放相同的数,这样的话我们可以用并查集,假设最后有 num 个集合,为了保证这个数有 n 位,第一位就不能为 0,那么最后的答案是 ,时间复杂度为O(nm)
100分做法:这个做法实际上是30分基础上的一个优化,上面的做法对 ~(~)中每一位都合并了一次,是O(n)的复杂度,我们就从这个角度优化,每次合并的时候以区间来合并,而不是按照每一位来合并,这个怎么实现呢,用倍增,我们用 father[ i ][ j ] 表示以 i 为起点往后 2^j 个位置这个区间的父亲(父亲就是这个集合中某一区间的左端点),这样的话复杂度就降到了O(log n),然后我们把每个区间向下传递到更小的区间直到又传到某一位,再统计答案就行了
【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
#define L 25
#define N 100005
#define mod 1000000007
using namespace std;
int father[N][L];
int find(int x,int y)
{
if(father[x][y]==x) return x;
return father[x][y]=find(father[x][y],y);
}
void merge(int x,int y,int i)
{
x=find(x,i);
y=find(y,i);
if(x!=y)
father[x][i]=y;
}
int main()
{
int n,m,i,j;
int l1,r1,l2,r2;
int num=0,ans=9;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i)
for(j=0;j<=L;++j)
father[i][j]=i;
for(i=1;i<=m;++i)
{
scanf("%d%d",&l1,&r1);
scanf("%d%d",&l2,&r2);
for(j=L;j>=0;--j)
{
if(l1+(1<<j)-1<=r1)
{
merge(l1,l2,j);
l1+=(1<<j);
l2+=(1<<j);
}
}
}
for(j=L;j;--j)
{
for(i=1;i+(1<<j)-1<=n;++i)
{
merge(i,find(i,j),j-1);
merge(i+(1<<j-1),find(i,j)+(1<<j-1),j-1);
}
}
for(i=1;i<=n;++i)
if(find(i,0)==i)
num++;
for(i=2;i<=num;++i)
ans=(10ll*ans)%mod;
printf("%d",ans);
return 0;
}