[luogu4128][shoi2006]有色圖

前言

計數題

題目相關

題目鏈接

題目大意

nn個點的完全圖,對邊染色(顏色有mm種),求本質不同的染色方案數,答案對pp取模

數據範圍

1n53,1m1000,1p1091\le n\le53,1\le m\le1000,1\le p\le10^9

題解

我們乍一看是染色問題,我們就想到了Polya定理
l=1GaiGkλ(ai)l=\frac{1}{|G|}\sum_{a_i\in G}k^{\lambda(a_i)}
我們可以用其式子來計算
但是我們發現染色的是邊而不是點,而是邊,所以我們計算的置換是對於邊的
我們考慮先不枚舉邊置換,而是枚舉點的置換,容易發現,一個點置換,其對應的邊置換是唯一確定的
這樣一來,就有了方便的枚舉置換方式
我們發現,點的置換有n!n!種,我們發現53!53!的複雜度顯然不能接受
我們考慮不直接枚舉所有點置換,而是枚舉本質不同的點置換,本質相同的點置換,對應的邊置換也是本質相同的
這樣的話複雜度就是nn的整數劃分了,隨便寫個程序就可以發現當n=53n=53時,整數劃分是31053*10^5級別的,目前看起來可以接受
code

#include<cstdio>
#include<cctype>
#define rg register
template<typename T>inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template<typename T>inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template<typename T>inline void print(const T x){if(x>=0)printe(x);else putchar('-'),printe(-x);}
int n,ans;
void dfs(const int t,const int h)
{
	if(t==0)ans++;
	else for(rg int i=h;i<=t;i++)dfs(t-i,i);
}
int main()
{
	read(n);
	dfs(n,1);
	print(ans);
	return 0;
}

我們考慮現在得到了一個整數劃分,其有kk個循環,按循環長度排序後的第ii個循環長度爲LiL_iL1L2LkL_1\le L_2\le···\le L_k
我們思考對於這樣一個點置換,其對應的邊置換的循環數量(在Polya中要求的東西)
兩個端點在同一個長度爲LL的點循環內的情況:
我們發現,對於任意一條邊,將其兩邊的點轉動,這樣就可以產生一個大小爲LL的邊循環
然後這個時候有個特例,當LL爲偶數時,有一個大小爲L2\frac{L}{2}的循環,即環上每組相對的點所組成的循環
即:
LL爲奇數時,循環數爲(L2)L=L(L1)/2L=L12\frac{\binom{L}{2}}{L}=\frac{L*(L-1)/2}{L}=\frac{L-1}2
LL爲偶數時,循環數爲(L2)+L2L=L(L1)/2+L/2L=L2\frac{\binom{L}{2}+\frac{L}{2}}{L}=\frac{L*(L-1)/2+L/2}{L}=\frac L2
綜上,這裏的邊循環數爲L2\left\lfloor\frac L2\right\rfloor
兩個端點分別在長度爲LaL_aLbL_b的點循環內的情況:
容易發現,這裏的邊循環長度爲lcm(La,Lb)lcm(L_a,L_b)
那麼循環數量爲LaLblcm(La,Lb)=gcd(La,Lb)\frac{L_a*L_b}{lcm(L_a,L_b)}=gcd(L_a,L_b)

這樣的話,對於一個枚舉到的本質不同的點循環,其對Poyla定理中後面那個sigma的指數CC就爲
C=i=1kLi2+i=1kj=i+1k(Li,Lj)C=\sum_{i=1}^k\left\lfloor\frac {L_i}2\right\rfloor+\sum_{i=1}^k\sum_{j=i+1}^k(L_i,L_j)
我們也知道顏色數,現在還需要統計這種點循環的出現次數
容易發現,出現次數SS可以直接計算
我們設TiT_i爲長度爲iiLL出現的次數(另外,定義0!=10!=1
S=i=1k(Li1)!(nj=1i1LjLi)i=1nTi!=n!i=1nTi!i=1kLiS=\frac{\prod_{i=1}^k(L_i-1)!\binom{n-\sum_{j=1}^{i-1}L_j}{L_i}}{\prod_{i=1}^{n}T_i!}=\frac{n!}{\prod_{i=1}^{n}T_i!*\prod_{i=1}^{k}L_i}
這樣的話我們就能快速的求出SS
最終答案就可以算出來了
l=SmCn!l=\frac{S*m^C}{n!}

代碼

這份代碼爲了方便理解,寫的複雜度比較大,實際可以有更好的寫法使程序跑得更快

#include<cstdio>
#include<cctype>
#define rg register
typedef long long ll;
template<typename T>inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template<typename T>inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template<typename T>inline void print(const T x){if(x>=0)printe(x);else putchar('-'),printe(-x);}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
ll n,m,p,L[61],fac[61],ans,tim[61];
inline ll pow(ll x,ll y)
{
	ll res=1;
	for(;y;y>>=1,x=x*x%p)if(y&1)res=res*x%p;
	return res;
}
void dfs(const int step,const int t,const int h)
{
	if(t==0)
	{
		ll xs=0,xf=1;
		for(rg int i=1;i<step;i++)
		{
			xs+=L[i]>>1;
			for(rg int j=i+1;j<step;j++)xs+=gcd(L[i],L[j]);
			xf=xf*L[i]%p;
		}
		for(rg int i=1;i<=n;i++)xf=xf*fac[tim[i]]%p;
		ans+=pow(m,xs)*pow(xf,p-2)%p;
	}
	else for(rg int i=h;i<=t;i++)
	{
		L[step]=i;
		tim[i]++;
		dfs(step+1,t-i,i);
		tim[i]--;
	}
}
int main()
{
	read(n),read(m),read(p);
	fac[0]=1;for(rg int i=1;i<=60;i++)fac[i]=fac[i-1]*i%p;
	dfs(1,n,1);
	print(ans%p);
	return 0;
}

總結

代碼非常的清真,良好的計數技巧配上Poyla定理就能解決這題
計數好難

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章