[SCOI2016]萌萌哒(倍增+并查集)

我的并查集太蒻了

题意

给定一个n位的没有前导零的数,和m个限制((n,m leq 1e5)),每个限制给定(l,r,L,R),表示([l,r])([L,R])这些位上的数分别相同,求这个数有多少种,数据保证(R-L==r-l)

思路

询问多少种不同的数等价于求有多少位不受其它位的约束可以自由选择,那么(ans=9*10^{cnt-1})

暴力显然就是对于每一个限制都直接枚举每一位,将它们放入并查集中,这样做时间复杂度是(nm*)玄学

考虑优化暴力,即不能枚举每一位,而是用一个区间表示一些位,首先容易想到用(f(i,j))表示从i开始后面j位这一个区间,但是空间换时间,会爆炸,这时便可以使用倍增,使得空间时间两不误

(f(i,j))表示从i开始,长为(2^j)的一个区间,对于限制区间([l,r]),将其二进制拆分,拆成log个后分别合并,便完成了合并

对于询问,之前可以直接find(i)==i?得到i是否不受约束,但是由于我们用区间表示一系列点,导致单点的信息缺失,不能直接查询一个点是否受约束,这时候就需要对区间进行拆分,找到(f(i,j))对应的位置k,将两个区间都从中间分成两半,分别连一条边即可完成从(f(i,j))(f(i,j-1))(f(i+(1<<(j-1)),j-1))的约束下放

Code:

#include<bits/stdc++.h>
#define N 100005
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
int n,m,fa[N][21];
template <class T>
void read(T &x)
{
	char c;int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
ll quickpow(ll a,ll b)
{
	ll ret=1;
	while(b)
	{
		if(b&1) ret=ret*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ret;
}
int find(int x,int y) {return fa[x][y]==x ? fa[x][y] : fa[x][y]=find(fa[x][y],y);}
int main()
{
	read(n);read(m);
	for(int j=0;j<=20;++j)
		for(int i=1;i<=n;++i)
			fa[i][j]=i;
			
	for(int i=1;i<=m;++i)
	{
		int l,r,L,R;
		read(l);read(r);read(L);read(R);
		int len=(R-L+1),ll=l,LL=L;
		for(int j=20;j>=0;--j)
		{
			if(len>>j&1)
			{
				fa[find(fa[LL][j],j)][j]=find(fa[ll][j],j);
				ll+=(1<<j);
				LL+=(1<<j);
			}
		}
	}
	for(int j=20;j;--j)
	{
		for(int i=n+1-(1<<j);i>=1;--i)
		{
			int k=find(fa[i][j],j);
			fa[find(fa[i][j-1],j-1)][j-1]=find(fa[k][j-1],j-1);
			fa[find(fa[i+(1<<(j-1))][j-1],j-1)][j-1]=find(fa[k+(1<<(j-1))][j-1],j-1);
		}
	}
	int cnt=0;
	for(int i=1;i<=n;++i) if(find(i,0)==i) cnt++;
	cout<<9LL*quickpow(10,cnt-1)%mod<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11253085.html