題意:有Sr張紅色牌、Sg張綠色牌和Sb張藍色牌,m種洗牌法(置換),兩種牌相同當且僅當一種能洗成另一種,保證洗牌法滿足封閉性、存在逆元,問有多少種染色方案。輸出答案模質數p的結果,Max{Sr,Sb,Sg}<=20,m<=60,m+1<p<100。
Burnside引理用于解決等價類計數問題。
設G是置換群,定義等價關系R:
R={(a,b)|a,b∈Ω,∃f∈G:b=f(a)}
設L是 Ω 中所有等價類,從每一類中任取一進制素構成的集合,則
|L|=∑g∈Gc1(g)|G|
其中 c1(g) 是 g 函數不動點的數目,即g作用其上得到它本身的元素個數。
證明:
設 Zk 是 k 的轉置不動類,Ek是 k 的等價類。
|L|=∑x∈L|Ex||Ex|=∑x∈L∑y∈Ex1|Ey|=∑x∈L∑y∈Ex|Zy||G|=∑x∈Ω|Zx||G|=∑g∈Gc1(g)|G|
其中第三個等号用到這樣一個等式:
|Zk||Ek|=|G|
證明如下:
顯然 Zk 是 G 的子群,由拉格朗日陪集定理知|G|是 |Zk| 的倍數……
子群的陪集是不相交的,如果 f,g 在 Zk 的同一個陪集内,則把它們分到一類,那麼 G 被劃分為一些子集,由陪集的性質,每個子集大小為|Zk|。
設 f=g∘h,g∈G,h∈Zk ,則 f(k)=g(k) ,即一類中的函數作用在 k 上得到同一個元素。顯然這些類與Zk形成一一對應,于是一共有 |Ek| 類。
問賀神這個等式怎麼證,賀神推薦了陪集的概念、拉格朗日定理,于是我腦補了以上幾段。目前也隻能給出這樣叙述不甚嚴謹的證明了QAQ
Polya定理是Burnside引理很顯然的推論。對于一個置換 f ,把它分解為m(f)個循環。 f 的不動點必須使得每個循環内的格子顔色相同。設共有d種顔色,那麼 c1(f)=dm(f) 。Polya定理:等價類的個數等于所有置換 f 的dm(f)的平均數。
學習Burnside引理和Polya定理要明确概念:等價類是 Ω 上的,置換和函數複合運算構成置換群 <G,∘> <script type="math/tex" id="MathJax-Element-178"> </script>。使用它們必須驗證描述等價關系的 G 滿足封閉性、存在機關元和逆元,結合律已由函數複合運算的性質保證。
讓我們來看一看bzoj 1004。由于每種顔色有數目限制,不能直接使用Polya定理的公式,但其思想仍可借鑒。将每種洗牌法分解為循環,每個循環内的格子必須同色。定義狀态為考慮到第幾個循環、每種顔色分别有多少可用,DP即可。四個次元中可以去掉一維顔色,這裡使用記憶化搜尋,事實上沒求解這些無用的狀态。時間複雜度O(mn3)。
題目僅保證封閉性和逆元的存在性,我們需要自己加上機關元。其實還應該驗證一下各個洗牌法互不相同,不過出題人挺良心。
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX_N = , MAX_C = ;
int p, a[MAX_N+], sz[MAX_N+], f[MAX_N+][MAX_C+][MAX_C+][MAX_C+];
bool vis[MAX_N+];
int inv(int x)
{
int y = ;
for (int n = p-; n; n >>= ) {
if (n & )
y = y*x%p;
x = x*x%p;
}
return y;
}
int dp(int k, int r, int b, int g)
{
if (k == )
return ;
int& x = f[k][r][b][g];
if (x >= )
return x;
x = ;
if (r >= sz[k])
x += dp(k-, r-sz[k], b, g);
if (b >= sz[k])
x += dp(k-, r, b-sz[k], g);
if (g >= sz[k])
x += dp(k-, r, b, g-sz[k]);
return x %= p;
}
int main()
{
int sr, sb, sg, m;
scanf("%d %d %d %d %d", &sr, &sb, &sg, &m, &p);
int n = sr+sb+sg;
memset(f, -, sizeof(f));
for (int i = ; i <= n; ++i)
sz[i] = ;
int ans = dp(n, sr, sb, sg);
for (int k = ; k <= m; ++k) {
for (int i = ; i <= n; ++i)
scanf("%d", &a[i]);
memset(vis, , sizeof(vis));
int cnt = ;
for (int i = ; i <= n; ++i) {
if (vis[i])
continue;
vis[i] = true, sz[++cnt] = ;
for (int j = a[i]; j != i; j = a[j])
vis[j] = true, ++sz[cnt];
}
memset(f, -, sizeof(f));
ans += dp(cnt, sr, sb, sg);
}
printf("%d\n", ans*inv(m+)%p);
return ;
}