Burnside引理與Polya定理
簡述:
Burnside 引理:
一類狀態在一個置換群 (\(G\)) 的作用下本質不同的狀態(不同等價類)個數
\(∣X/G∣=\dfrac{1}{|G|}*\sum\limits_{r\in G}c(r)\)
\(c(r)\)表示在 \(r\) 這一置換作用下不動點(狀態不變的方案)的個數。
證明略。
Polya定理:
\(c(r)=k^m\)
\(k\) 為當前置換不同迴圈的個數,\(m\)為可賦予顏色數。
即位於同一迴圈(可互相替代,專業叫等價類)的元素顏色相同才能成為不動點。
栗子:
不同的翻轉操作即為置換群。
對於向下 $90^\circ $ 的操作,中間四個面,左邊的面,右邊的面即為三個不同的迴圈(等價類)。
例題:
一:P4980 【模板】Pólya 定理
題意:
給定一個 \(n\) 個點,\(n\) 條邊的環,有 \(n\) 種顏色,給每個頂點染色,問有多少種本質不同的染色方案。
做法:
咳咳,不是我懶得寫,主要 Latex 太難打了。
說明一下那個 \(gcd\) 那點:
對於一個旋轉 \(k\) 次的操作,當前點旋轉 \(n/gcd(n,k)\) 次就會回到當前點,即 \(k * ( n / gcd ( n,k ) ) %n == 0\) 。因此一個迴圈有 \(n/gcd(n,k)\) 節點,所以一共有 \(gcd(n,k)\) 個迴圈。
code:
#include<bits/stdc++.h> using namespace std; #define ll long long const ll N=1e5+8; ll n; const ll mod=1e9+7; ll qm(ll x,int p) { if(p==0) return 1; ll tmp=qm(x,p>>1); if(p&1) return tmp*tmp%mod*x%mod; return tmp*tmp%mod; } ll read() { ll x=0; char c=getchar(); while(c>'9'||c<'0') c=getchar(); while(c>='0'&&c<='9'){ x=x*10; x+=c-'0'; c=getchar(); } return x; } ll ol(ll x) { ll ans=x; for(int i=2;i*i<=x;i++) if(x%i==0){ ans=ans*(i-1)%mod*qm(i,mod-2)%mod; while(x%i==0) x/=i; } if(x>1) ans=ans*(x-1)%mod*qm(x,mod-2)%mod; return ans%mod; } int main() { int T; cin>>T; while(T--) { n=read(); ll ans=0; for(ll i=1;i*i<=n;i++) { if(n%i) continue; ans=(ans+ol(n/i)*qm(n,i-1)%mod)%mod; if(i*i!=n) ans=(ans+ol(i)*qm(n,n/i-1)%mod)%mod; } printf("%lld\n",ans); } }
還有道一樣的題:【例題1】彩色項鍊1
改成 \(m\) 種顏色還是一樣的,只是改一下對應底數就行:
【例題2】彩色項鍊2
還有一道:P5233 [JSOI2012]愛之項鍊
本題還不清楚為啥 T 了。
關於波利亞定理的運用是一樣的,只是多了個 \(dp\) 和數學求斐波那契數列一類的通項公式的方法,後者還沒學會,不過可以用矩陣代替。
二:P4727 [HNOI2009]圖的同構計數
題意:
邊有兩種顏色,
求含 N 個點的圖在同構意義下不同構的圖的數目。
A圖與B圖被認為是同構的是指:A圖的頂點經過一定的重新標號以後,A圖的頂點集和邊集要完全與B圖一一對應。
做法:
對於自己來說可能更清楚的講解:
一個圖的置換,其實就是找一種排列來替換 \(1\thicksim n\)的點。
對於一個置換,將其拆為若干個互不影響的置換。
2 3 1
1 2 3
以上置換代表用 2 3 1 頂替 1 2 3。
例如:
5 3 2 1 4
1 2 3 4 5
將其拆為
5 1 4
1 4 5
和
3 2
2 3
下面將邊分為兩類。
一:兩端點在同一置換裡。
對於每個置換而言,將其按順序排好
例如
3 4 2 1
1 2 3 4
按 1 4 2 3 的順序排(因為元素是按這個順序流動的)。
排成一個正多邊形,會發現不同長度(即相隔不同個數的點)的邊屬於不同的等價類,因此有 \(\left[\dfrac{p[i]}{2}\right]\) 種不同的等價類,\(p[i]\) 為該置換點數。
二:兩端點在不同置換裡。
一條邊想轉回自己的位置要走 \(lcm(p[i],p[j])\) 下,即兩點同時回到原位,因此這 \(lcm(p[i],p[j])\) 條邊屬於一個等價類,共有 \(p[i]*p[j]\) 條邊,因此有 \(p[i]*p[j]/lcm(p[i],p[j])=gcd(p[i],p[j]\))。
綜上答案為:
\(b\) 同 \(p\),\(K\)為拆分出來的置換個數。
最後,為了使計算快捷,直接列舉(爆搜)本質不同的置換,即將 \(n\) 拆分。
考慮有多少種拆成 \(P\) 這種方案的。
用 \(\dfrac{n!}{\prod_{p[i]!}}\) 算出點的分配方案。
對於一個置換裡的點,相當於一個 \(n\) 邊型頂點的排列,即圓排列,因此有對於一個置換來說有 \((p[i]-1)!\) 種排列方案,總的就要乘上 \(\prod_{(p[i]-1)!}\)。
最後,對於長度相等的迴圈它們之間可以彼此交換,本質上是一樣的,因此還是會算重。設 \(c\) 表示表某個長度的迴圈的個數,則會算重 \(c!\) 倍。因此答案還需除以 \(\prod_{c[i]!}\) 。
算上除以的置換群大小 \(n!\),總答案為:
黑題完成!
code:
#include<bits/stdc++.h>
using namespace std;
const int N=65;
const int mod=997;
int n;
int p[N],tot;
int t[N];
int jc[N];
int ans=0;
int minn(int a,int b)
{
return a>b?b:a;
}
int maxx(int a,int b)
{
return a<b?b:a;
}
int qm(int x,int p)
{
if(p==0) return 1;
int tmp=qm(x,p>>1);
if(p&1) return tmp*tmp*x%mod;
return tmp*tmp%mod;
}
int gcd(int a,int b)
{
if(b==0) return a;
return gcd(b,a%b);
}
void work()
{
int k=0;
int tmp=1;
memset(t,0,sizeof(t));
for(int i=1;i<=tot;i++)
{
k+=p[i]/2;
t[p[i]]++;
tmp=tmp*qm(p[i],mod-2)%mod;
for(int j=1;j<i;j++)
k+=gcd(p[i],p[j]);
}
for(int i=1;i<=n;i++)
{
if(t[i]>1)
tmp=tmp*qm(jc[t[i]],mod-2)%mod;
}
ans=(ans+qm(2,k)*tmp%mod)%mod;
}
void dfs(int x,int h,int s)
{
if(!h) tot=x-1,work();
if(h<s) return;
for(int i=s;i<=h;i++)
{
p[x]=i;
dfs(x+1,h-i,i);
}
}
int main()
{
cin>>n;
jc[0]=1;
for(int i=1;i<=n;i++)
jc[i]=jc[i-1]*i%mod;
dfs(1,n,1);
cout<<ans<<endl;
}
如果有 \(m\) 種顏色也是一樣的:
P4128 [SHOI2006] 有色圖
卡常所得經驗:
\(1\).深搜別閒著沒事寫迭代加深,慢死了,這兩題的深搜可以多看一看。
\(2\).逆元可以預處理一下快一點。
黑題二倍經驗,好耶!