【burnside & polya】hnoi2008 cards hnoi2009 count

 


      寒假的時候被陳老師講的組合數學死去活來,後來再去看一次仍然沒看懂,今天又看了一次,終於看懂了(不容易啊)。

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章