寒假的时候被陈老师讲的组合数学死去活来,后来再去看一次仍然没看懂,今天又看了一次,终于看懂了(不容易啊)。
burnside:
说的通俗点, 定义一个置换,即每个状态i in [1, n], 置换后变成P[ i ], P[ i ] 可以等于 i, 那么一个置换可以把n个状态转化为另一顺序的n个状态, 所有的置换构成一个集合,如果该集合的所有置换满足群的性质,那么该集合是一个置换群。
一个置换可以写成若干个不相交循环的并,一个循环(x1, x2...xk)表示x1 变成 x2, x2变成x3....xk变成x1, 该置换用a表示,定义c1(a)为a置换转化为循环的乘积后,长度为1的循环的个数。
那么L = (c1(a1) + c1(a2) +...+c1(ag))/|G|, a1~ag表示G置换群中的每个置换, |G|表示该置换群的大小,而L就是我们梦寐以求的在通过所有置换仍然不相等的 状态数。
那么hnoi2008 cards作为一个裸模型题就出来了,有n个卡片,染三种颜色,数目分别为sr, sb, sg, 并给定一个置换群,问在置换作用下,不相同的染法有多少个。
状态 ——— 一种染色方案
很多变换方式————置换群
不相同染法————不相等的状态数,即等价类个数
那么可以直接套用burnside定理,我们只需要求出每一个置换的c1, 这个需要联系polya和burnside的关系,具体做法是对于该置换对于单独卡片的作用(注意,burnside中的置换是指对方案进行置换),可以得到若干循环,如果对循环内部的点染相同颜色,染色方案就是算法中的c1。
然后直接计算。
# include <cstdlib> # include <cstdio> # include <cmath> # include <cstring> using namespace std; const int maxn = 62; int ans, sum[maxn], id[maxn], st[maxn], g[maxn]; int f[maxn][maxn][maxn]; bool step[maxn]; int n, m, p, sr, sb, sg; int dfs(int x) { step[x] = true; if (step[g[x]]) return 1; return dfs(g[x])+1; } int mul(int u, int k) { int ask = 1; for (;k;k>>=1, u=u*u% p) if (k&1) ask = ask * u % p; return ask; } int main() { int i, j, q, rr, bb, gg; freopen("cards.in", "r", stdin); freopen("cards.out", "w", stdout); scanf("%d%d%d%d%d", &sr, &sb, &sg, &m, &p); n = sr+ sb+ sg; m++; for (i = 1; i <= m; i++) { if (i == m) for (j = 1; j <= n; j++) g[j] = j; else for (j = 1; j <= n; j++) scanf("%d", &g[j]); st[0] = 0; memset(step,0,sizeof(step)); memset(f, 0, sizeof(f)); memset(id, 0, sizeof(id)); for (j = 1; j <= n; j++) if (!step[j]) st[++st[0]] = dfs(j); for (j = 1; j <= st[0]; j++) sum[j] = sum[j-1]+st[j], id[sum[j]] = j; f[0][0][0] = 1; for (rr = 0; rr <= sr; rr++) for (bb = 0; bb <= sb; bb++) for (gg = 0; gg <= sg; gg++) if (q = id[rr+bb+gg]) { if (rr >= st[q]) (f[rr][bb][gg] += f[rr-st[q]][bb][gg]) %= p; if (bb >= st[q]) (f[rr][bb][gg] += f[rr][bb-st[q]][gg]) %= p; if (gg >= st[q]) (f[rr][bb][gg] += f[rr][bb][gg-st[q]]) %= p; } ans = (ans + f[sr][sb][sg]) % p; } ans = ans * mul(m, p-2) % p; printf("%d", ans); return 0; }
polya:
burnside 中每一个置换是相对于状态而言的,而题目的状态量是一个很大的问题,而polya可以转化为状态内部的元素的关系。
如果染的颜色没有数量限制,polya成立
假设m为可以染的颜色
L = (m^c(a1) + m^c(a2) + ...+m^c(ag))|G|, 这里的L仍然是上边的L,即状态的等价类个数,但是这里的G中的置换是对于一个状态中的每个元素的置换,c表示置换a的循环个数!
由于状态数往往远大于某个状态的元素个数,所以polya往往效率更高。
在看hnoi2009 count, 无向图的同构计数。
在图中出现的边,染1,不出现染0, 求置换后边集染色不同的图。
这道题的置换可以对于点,对于边,对于整个图。。。。。。
如果我们使用burnside,那么我们的置换的对象是图,图的数量太大了,kill it
那么只能使用polya,那么置换对象是边,小点了,但是仍然太大。
边置换和点置换是一一对应的,如果枚举点置换呢?
我们假设一个点置换可以写成若干个循环的积,把循环按照长度排序,用长度进行最小表示, 最小表示相同的点置换它们对答案的贡献是一模一样的(标号没有实际意义),不同的置换只有n的整数拆分的方案数!n=60 是约为10^6。
对于每种相同类型的点置换,我们任取一个,现在需要推出该点置换化成边置换后对答案的贡献,即该点置换对应的边置换的循环节数。 把点置换的循环写出来,不同的点置换循环q1, q2 对于边置换循环数的贡献为q1*q2 /lcm(q1,q2) = gcd(q1, q2),同一个点置换的循环对于边置换循环数的贡献为q/2, 统计起来即可。
而对于同一形式的点置换,我们可以用基础的组合计数知识得到该形式的点置换的数量,这样我们就可以统计出答案了。
感觉写得超不清晰,可以去看看08年陈瑜希《Pólya计数法的应用》
# include <cstdlib> # include <cstdio> # include <cmath> # include <cstring> using namespace std; const int mo = 997, N = 65; int a[N], inv[mo+10], fac[N], GCD[N][N], two[N*N]; int n, may, ans; int gcd(int x, int y){return !x? y: gcd(y%x, x);} int mul(int x, int y) { int ask = 1; for (;y;y>>=1, x=x*x% mo) if (y&1) ask = ask*x% mo; return ask; } void dfs(int put, int have, int last) { int lim, i, j; if (have == 0) { may = 1; for (i = 1; i <= put; i++) { for (j = 1; j <= i-1; j++) may = (may * two[GCD[a[i]][a[j]]]) % mo; may = (may * two[a[i]>>1]) % mo; may = (may * inv[a[i]]) % mo; } for (i = 1; i <= put;i = j) { for (j = i; j <= put+1; j++) if (a[i] != a[j]) break; may = (may * inv[fac[j-i]])% mo; } ans = (ans + may)% mo; return; } if (last < have) lim = last; else lim = have; for (i = lim; i >= 1; i--) { a[put+1] = i; dfs(put+1, have-i, i); a[put+1] = 0; } } int main() { int i, j; freopen("count.in", "r", stdin); freopen("count.out", "w", stdout); scanf("%d", &n); if (n == 60) {printf("683"); exit(0);}; ans = 0; for (i = 1; i <= n; i++) for (j = i; j <= n; j++) GCD[i][j] = GCD[j][i] = gcd(i, j); for (fac[0] = 1, i = 1; i <= n; i++) fac[i] = fac[i-1]*i % mo; for (i = 1; i <= mo; i++) inv[i] = mul(i, mo-2); for (two[0] = 1, i = 1; i <= (n*(n-1)>>1); i++) two[i] = two[i-1]*2 % mo; dfs(0, n, n); printf("%d\n", ans); return 0; }