[SCOI2016]萌萌哒
博客食用更佳bossbaby's blog
思路
并查集
每次使两位相同,相当于在计数时把两个位当成一位来算
所以我们可以用并查集,在每次操作时把两个位用并查集并在一起,查找时用,设最后集合个数为M
那么答案是\(9*(M-1)^{10}\)
因为和1并在一起的数只有9种选择(首位不为0)
但是这样的话复杂度最大是\(O(10^{10})(n,m<=10^5)\)
所以不行
线段树
于是我们考虑线段树的区间操作
可惜不行...
倍增(st表)
另外一种区间操作就是st表
\(st[i][j]\)储存从\(i\)开始往后\(2^j\)的区间
那么就用并查集来判断每个区间的连通性,
每次合并时就用2进制从大到小分成区间合并
比如从\(l_1\)开始的23个数
就分成\(st[l_1][4],st[l_1][2],st[l_1][1],st[l_1][0]\)
分别与\(st[l_2][4],st[l_2][2],st[l_2][1],st[l_2][0]\)合并
最后再把每个区间下放到它的一半这两个区间,最后下放到\(st[i][0]\)就可以统计了
代码
#include<bits/stdc++.h>
#define N 100010
#define P 1000000007
using namespace std;
int st[N][20],fa[N*18],cnt=0,s[N*18];
int n,m;
int find(int x){
if(x!=fa[x])fa[x]=find(fa[x]);
return fa[x];
}
void unionn(int x,int y,int k){
int r1=find(st[x][k]),r2=find(st[y][k]);
if(r1>r2)swap(r1,r2),swap(x,y);
fa[r2]=r1;
find(y);
}//并查集
template<typename T>void read(T&x){
x=0;
char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c))x=x*10+c-'0',c=getchar();
}//快读
template<typename T>void write (const T x){
if(x<0){
putchar('-');
return write(-x);
}
if(x>9)write(x/10);
putchar(x%10+'0');
}//快写
int main(){
read(n);read(m);
if(n==1){
write(10);return 0;
}
for(int j=0;j<=18;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[i][j]=++cnt,s[cnt]=i,fa[cnt]=cnt;
for(int i=1;i<=m;i++){
int l1,r1,l2,r2;
read(l1),read(r1),read(l2),read(r2);
for(int j=18;j>=0;j--){
if(l1+(1<<j)-1<=r1){
unionn(l1,l2,j);
l1+=(1<<j);
l2+=(1<<j);
}
}
}//二进制拆分并合并
for(int j=18;j>=1;j--){
for(int i=1;i+(1<<j)-1<=n;i++){
int f=find(st[i][j]),sta=s[f];
if(sta==i)continue;
unionn(i,sta,j-1);
unionn(i+(1<<(j-1)),sta+(1<<(j-1)),j-1);
}
}//分成两半最后拆成st[i][0]
long long ans=9;
for(int i=2;i<=n;i++)
if(fa[st[i][0]]==st[i][0])ans=(1LL*ans*10)%P;//统计
write(ans);
}