[SCOI2016]萌萌哒
mrclr两周前做的题让蒟蒻的我现在做?
第一眼组合计数,如果把数字相同的数位看作一个整体,除了第一位不能为零,剩下的每一位都有$0$~$9$十种。
设不同的位数为$x$,那么答案即为$9*10$x-1
给出两段相同的区间,可以把它们看作单独的一位一位对应,用并查集把它们合并。
复杂度为$O(n^2)$。
考虑优化。发现修改操作有$n$次,查询只有$1$次。
有效的修改操作最多只有$n-1$次,所以一定有重复的操作。
把复杂度平衡一下。合并区间时,直接将大区间合并,最后将合并标记从大到小下传。
这样,合并的复杂度变小,查询的复杂度变大了。用倍增——也就是二进制拆分的做法,可以让修改、查询的复杂度都变为$O(nlogn)$
$fa[x][i]$表示从$x$开始,长度为$2^i$的区间的父亲(区间),
合并时,将一段区间用二进制拆分成若干个区间并合并;
最后查询时,从最大的区间长度开始枚举,将$[x][i]$的左右两半区间分别与$fa[x][i]$的左右两半区间合并。
void pushdown() { for(int j = 20; j; j--) for(int i = 1; i+(1<<j)-1 <= n; i++) { int ii = getfa(i,j); merge(i,ii,j-1); //左一半 merge(i + (1<<(j-1)),ii + (1<<(j-1)),j-1);//右一半 } }
最后查询并查集个数即可w
代码如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> #define MogeKo qwq #define ll long long using namespace std; const int maxn = 1e5+10; const ll mod = 1e9+7; int n,m,fa[maxn][25]; int l1,l2,r1,r2; ll cnt; ll qpow(ll a,ll b) { ll ans = 1; ll base = a; while(b) { if(b&1) (ans *= base) %= mod; (base *= base) %= mod; b >>= 1; } return ans%mod; } void init() { for(int i = 1; i <= n; i++) for(int j = 0; j <= 20; j++) fa[i][j] = i; } int getfa(int x,int k) { if(fa[x][k] == x)return x; return fa[x][k] = getfa(fa[x][k],k); } void merge(int x,int y,int k) { int xx = getfa(x,k); int yy = getfa(y,k); fa[xx][k] = yy; } void pushdown() { for(int j = 20; j; j--) for(int i = 1; i+(1<<j)-1 <= n; i++) { int ii = getfa(i,j); merge(i,ii,j-1); merge(i + (1<<(j-1)),ii + (1<<(j-1)),j-1); } } int main() { scanf("%d%d",&n,&m); if(n == 1) { printf("10"); return 0; } init(); for(int i = 1; i <= m; i++) { scanf("%d%d%d%d",&l1,&r1,&l2,&r2); for(int j = 20; j >= 0; j--) if(l1+(1<<j)-1 <= r1) { merge(l1,l2,j); l1 += (1<<j); l2 += (1<<j); } } pushdown(); for(int i = 1; i <= n; i++) if(fa[i][0] == i)cnt++; printf("%lld",(9*qpow(10,cnt-1))%mod); return 0; }