1. 程式人生 > 其它 >SCOI2016 萌萌噠 題解

SCOI2016 萌萌噠 題解

實在是一個妙題

我們首先考慮兩個區間完全相等可以轉化為對應點相等,對應的點相等即可以看作他們是一個相同的點。

於是我們有一個暴力:利用並查集,相同的點合併到一起,最後可以得到有多少個並查集。然後,我們可以用簡單的計數知識可以知道最後的答案就是\(9*10^{tot-1}\),因為最高位不能為0。

考慮這樣做的複雜度的預處理在\(O(N^2)\),而查詢答案則是\(O(N)\),複雜度不均衡,我們要考慮一種方法讓他的複雜度都均衡為\(O(N\log N)\)

於是我們可以發現,並查集的操作有可合併性,我們可以利用倍增實現優化,即:開大約20個並查集,表示某一個點往後\(2^i\)的區間和某一區間相同,這樣我們可以通過倍增完成預處理,再分裂大的並查集為最小的,最後查詢答案,每一步的複雜度都是\(O(N\log N)\)

,非常OK。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
#define orz cout<<"lyakioi!!!!!!!!!!!!!!!!!"<<endl
inline int r(){int s=0,k=1;char c=getchar();while(!isdigit(c)){if(c=='-')k=-1;c=getchar();}while(isdigit(c)){s=s*10+c-'0';c=getchar();}return s*k;}
int fa[3000001],t[1000001][25],n,m,cnt,mp[3000001];
bool b[3000001];
const int mod=1e9+7;
int father(int x)
{
	if(fa[x]!=x)fa[x]=father(fa[x]);
	return fa[x];
}
void unit(int x,int y)
{
	int fax=father(x);
	int fay=father(y);
	fa[fax]=fay;
}
int pw(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans*=a,ans%=mod;
		a*=a;
		a%=mod;
		b>>=1;
	}
	return ans;
}
signed main()
{
	n=r();m=r();
	int now=1,mx=0;
	while(1)
	{
		if(now>n)
		{
			mx--;
			break;
		}
		now*=2;mx++;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=mx;j++)
		{
			t[i][j]=(++cnt);
			mp[cnt]=i;
		}
	}
	for(int i=1;i<=cnt;i++)fa[i]=i;
	int l1,r1,l2,r2;
	for(int i=1;i<=m;i++)
	{
		l1=r();r1=r();l2=r();r2=r();
		if(l1>l2)
		{
			swap(l1,l2);
			swap(r1,r2);
		}
		int k=0,len=r1-l1+1;
		while(len)
		{
			if(len&1)
			{
				unit(t[l1][k],t[l2][k]);//?
				l1+=(1<<k);
				l2+=(1<<k);
			}
			k++;
			len>>=1;
		}
	}
//	orz;
	for(int i=mx;i;i--)
	for(int j=1;j<=n;j++)
	{
		int x=t[j][i];
		int fax=father(x);
		if(x==fax)continue;
		int y=mp[fax];//連往別人 
		unit(t[j][i-1],t[y][i-1]);
		unit(t[j+(1<<(i-1))][i-1],t[y+(1<<(i-1))][i-1]);
	}
//	orz;
	int tot=0;
	for(int i=1;i<=n;i++)
	{
		int x=father(t[i][0]);
		if(!b[x])
		{
			tot++;
			b[x]=1;
		}
	}
	cout<<9*pw(10,tot-1)%mod;
}