【burnside & polya】hnoi2008 cards hnoi2009 count

来源:互联网 发布:网络编程入门书籍 编辑:程序博客网 时间:2024/05/17 03:12

 


      寒假的时候被陈老师讲的组合数学死去活来,后来再去看一次仍然没看懂,今天又看了一次,终于看懂了(不容易啊)。

     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;}