1. 程式人生 > 其它 >Burnside引理和Polya定理題集

Burnside引理和Polya定理題集

Burnside引理和Polya定理單元測試

\(~~~~\) 不想做新題來水題解

A.The Colored Cubes

題意

\(~~~~\) 求給一個正方形的六個面塗上 \(n\) 種顏色的方案數。兩種方案不同指兩種方案之間不能通過旋轉得到。

\(~~~~\) \(1\leq n< 1000\)

題解

\(~~~~\) 正方體關注點的置換:DIY Cube;

\(~~~~\) 正方體關注邊的置換:Cubes

\(~~~~\) 正方體關注面的置換:本題。

\(~~~~\) 具體方法如圖:

\(~~~~\) 然後直接套用 \(\texttt{P}\acute{o}\texttt{lya}\)

定理即可,得到答案為:

\[\dfrac{n^6+8n^2+12n^3+3n^4}{24} \]

程式碼

檢視程式碼
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
ll qpow(ll a,ll b)
{
	ll ret=1;
	while(b)
	{
		if(b&1) ret=ret*a;
		b>>=1;a=a*a;
	}
	return ret;
}
ll n;
int main() {
	while(scanf("%lld",&n)&&n)
		printf("%lld\n",(qpow(n,6)+8*qpow(n,2)+12*qpow(n,3)+3*qpow(n,4))/24);
	return 0;
}

B.Necklace

題意

\(~~~~\) 求使用 \(a\) 個白色,\(b\) 個灰色,\(c\) 個黑色珠子組成項鍊的本質不同方案數。兩種方案若能通過旋轉翻轉得到則本質相同。

\(~~~~\) \(3\leq a+b+c\leq 40\)

\(~~~~\) 洛谷難度虛高。

\(~~~~\)\(n=a+b+c\) ,考慮旋轉的置換群的集合為 \(\{\text{旋轉}0\text{位,},\text{旋轉}1\text{位,},\dots \text{旋轉}n-1\text{位,}\}\) ,則由模板題可知當旋轉 \(k\) 位時,共有 \(\gcd(n,k)\) 個輪換,每個輪換內的 \(\dfrac{n}{\gcd(n,k)}\)

個珠子都必須是同色。由於有珠子數量的限制,不方便直接用\(\texttt{P}\acute{o}\texttt{lya}\) 定理,因此運用在 Pólya 定理學習筆記5.3 裡面的套路,對珠子進行類似於揹包的 DP 即可求解。

\(~~~~\) 然後考慮翻轉操作的置換群的集合,此時需要將 \(n\) 分為奇數和偶數討論:

  • \(n\) 為奇數時:對稱軸必定只過一個點,此時的置換屬於 \((1)^1(2)^{\frac{n-1}{2}}\) ,共 \(n\) 條這樣的對稱軸;

  • \(n\) 為偶數時:

    • 對稱軸過兩個點,此時的置換屬於 \((1)^2(2)^{\frac{n-2}{2}}\) ,共有 \(\frac{n}{2}\) 條這樣的對稱軸;

    • 對稱軸過兩條邊,此時的置換屬於 \((2)^{\frac{n}{2}}\) ,共有 \(\frac{n}{2}\) 條這樣的對稱軸。

\(~~~~\) 綜上,當有長為 \(1\) 的輪換與其他輪換複合時,列舉該輪換的珠子顏色,然後對剩下的輪換進行上面的 \(\texttt{DP}\) 即可,類似 Cubes 的套路。

檢視程式碼
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
int a[5],tot,b[5];
ll dp[42][42][42],Ans=0;
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
ll Get(int d)
{
	for(int i=0;i<=a[1];i++) for(int j=0;j<=a[2];j++) for(int k=0;k<=a[3];k++) dp[i][j][k]=0;
	int other=tot/d;
	for(int i=1;i<=3;i++) if(a[i]%other) return 0;
	dp[0][0][0]=1;
	for(int i=1;i<=d;i++)
	{
		for(int j=a[1];j>=0;j--)
		{
			if(j%other) continue;
			for(int k=a[2];k>=0;k--)
			{
				if(k%other) continue;
				for(int l=a[3];l>=0;l--)
				{
					if(l%other) continue;
					if(j>=other) dp[j][k][l]+=dp[j-other][k][l];
					if(k>=other) dp[j][k][l]+=dp[j][k-other][l];
					if(l>=other) dp[j][k][l]+=dp[j][k][l-other];
				}
			}
		}
	}
	return dp[a[1]][a[2]][a[3]];
}
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
ll phi(ll n)
{
	ll ret=n;
	for(ll i=2;i*i<=n;i++)
	{
		if(n%i==0)
		{
			ret=ret/i*(i-1);
			while(n%i==0) n/=i;
		}
	}
	if(n>1) ret=ret/n*(n-1);
	return ret;
}
int main() {
	int T;
	read(T);
	while(T--)
	{
		Ans=0;
		read(a[1]);read(a[2]);read(a[3]);tot=a[1]+a[2]+a[3];
		for(int i=1;i*i<=tot;i++)
		{
			if(tot%i) continue;
			Ans+=Get(gcd(i,tot))*phi(tot/i);
			if(tot/i!=i) Ans+=Get(gcd(tot/i,tot))*phi(i);
		}
		if(tot&1)
		{
			ll tmp=0;
			for(int i=1;i<=3;i++)
			{
				if(a[i])
				{
					a[i]--;tot--;
					tmp+=Get(tot/2);
					a[i]++;tot++;
				}
			}
			Ans+=tmp*tot;
		}
		else
		{
			Ans+=(tot/2)*Get(tot/2);
			ll tmp=0;
			for(int i=1;i<=3;i++)
			{
				if(a[i])
				{
					tot--;a[i]--;
					for(int j=1;j<=3;j++)
					{
						if(a[j])
						{
							tot--;a[j]--;
							tmp+=Get(tot/2);
							tot++;a[j]++;
						}
					}
					tot++;a[i]++;
				}
			}
			Ans+=tmp*(tot/2);
		}
		printf("%lld\n",Ans/(tot<<1));
	}
	return 0;
}

C.Color

題意

\(~~~~\)洛谷模板題

題解

\(~~~~\) 卡常,噁心

\(~~~~\) 注意到給出的模數不一定是質數,甚至不一定與 \(n\) 互質,所以做快速冪的時候少乘一次即可。

\(~~~~\) 具體思路見 Pólya 定理學習筆記5.2

程式碼

程式碼

檢視程式碼
#include <bits/stdc++.h>
#define ll long long
using namespace std;
bool NotPrime[1000005];
int Prime[100005],cnt,MOD;
void makePrime(int N)
{
	NotPrime[0]=NotPrime[1]=true;
	for(int i=2;i<=N;i++)
	{
		if(!NotPrime[i]) Prime[++cnt]=i;
		for(int j=1;j<=cnt&&i*Prime[j]<=N;j++)
		{
			NotPrime[i*Prime[j]]=true;
			if(i%Prime[j]==0) break;
		}
	}
}
int qpow(ll a,int b)
{
	ll ret=1;
	while(b)
	{
		if(b&1) ret=ret*a%MOD;
		b>>=1;a=a*a%MOD;
	}
	return ret;
}
int phi(int n)
{
	int ret=n;
	for(int i=1;i<=cnt&&Prime[i]*Prime[i]<=n;i++)
	{
		if(n%Prime[i]==0)
		{
			ret=ret-ret/Prime[i];
			while(n%Prime[i]==0) n/=Prime[i];
		}
	}
	if(n>1) ret=ret-ret/n;
	return (ret%MOD+MOD)%MOD;
}
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
int main() {
	int T;
	read(T);
	makePrime(40000);
	while(T--)
	{
		ll Ans=0,n;
		read(n);read(MOD);
		for(ll i=1;i*i<=n;i++)
		{
			if(n%i) continue;
			Ans=(Ans+1ll*qpow(n,i-1)*phi(n/i)%MOD)%MOD;
			if(n/i!=i) Ans=(Ans+1ll*qpow(n,n/i-1)*phi(i)%MOD)%MOD;
		}
		printf("%lld\n",Ans);
	}
	return 0;
}

D.Birthday Toy

題意

\(~~~~\) \(n\) 個小珠子加一個大珠子,大珠子放在中間,小的圍著它形成一個圓形並與之相鄰,給所有珠子塗上 \(k\) 種顏色中的一種,在滿足任意相鄰珠子都不能同色的情況下的本質不同的塗色方案數是多少。兩種方案若能通過旋轉得到則本質相同,最後答案 \(\bmod 10^9+7\)

\(~~~~\) \(1\leq n,k\leq 10^9\) 且至少 \(1000\) 組資料。

題解

\(~~~~\) 一看先給大珠子塗上一種顏色,然後刪掉它和一種顏色,此時題目大致轉化為 Pólya 定理學習筆記5.3 ,最後答案即為該題答案 \(\times k\)

\(~~~~\) 但本題的顏色數十分巨大,所以我們需要想辦法規避用其開矩陣。我們發現本題若直接套用上述的題目,其轉移矩陣的特點是初始時主對角線全部為 \(0\) ,其餘全部為 \(1\) 。那麼考慮對這個矩陣作若干次快速冪後的結果是否也有特點。

\(~~~~\) 手推或者寫個程式找規律就會發現,對於大小為 \(m\times m\) 的矩陣 \(M\) 而言, \(M^d\) 其主對角線上所有數的值在 \(d\) 為奇數時等於 \(\frac{(m-1)^k-(m-1)}{m}\) ,偶數時等於 \(\frac{(m-1)^k+(m-1)}{m}\) ,而最後的主對角線總和 \(\times m\) 即可。

\(~~~~\) 然後我們就繞過了開超大矩陣的一步,其餘就和上述題目沒有區別了。

檢視程式碼
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
int n,m,K;ll Ans;
const int MOD=1000000007;
ll qpow(ll a,ll b)
{
	ll ret=1;
	while(b)
	{
		if(b&1) ret=ret*a%MOD;
		b>>=1;a=a*a%MOD;
	}
	return ret;
}
ll phi(ll n)
{
	ll ret=n;
	for(ll i=2;i*i<=n;i++)
	{
		if(n%i==0)
		{
			ret=ret/i*(i-1)%MOD;
			while(n%i==0) n/=i;
		}
	}
	if(n>1) ret=ret/n*(n-1)%MOD;
	return ret;
}
void Solve(int d)
{
	ll res;
	if(d&1) res=1ll*((qpow(m-1,d)-(m-1))%MOD+MOD)%MOD;
	else res=1ll*(qpow(m-1,d)+(m-1));
	Ans=(Ans+res%MOD*phi(n/d)%MOD)%MOD;
}
int main() {
	while(~scanf("%d %d",&n,&m))
	{
		Ans=0;m--;
		for(int g=1;g*g<=n;g++)
		{
			if(n%g) continue;
			Solve(g);
			if(n/g!=g) Solve(n/g);
		}
		Ans=Ans*qpow(n,MOD-2)%MOD*(m+1)%MOD;
		printf("%lld\n",Ans);
	} 
	return 0;
}

\(~~~~\) 在本部落格中掛了那麼多次學習筆記的連結不去看看真的好嗎