1. 程式人生 > >【burnside & polya】hnoi2008 cards hnoi2009 count

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


相關推薦

burnside & polyahnoi2008 cards hnoi2009 count

      寒假的時候被陳老師講的組合數學死去活來,後來再去看一次仍然沒看懂,今天又看了一次,終於看懂了(不容易啊)。      burnside:      說的通俗點, 定義一個置換,即每個狀態i in [1, n], 置換後變成P[ i ], P[ i ] 可以等

BZOJ1005/1211[HNOI2008]明明的煩惱/[HNOI2004]樹的計數 Prufer序列+高精度

數量 最終 避免 desc inpu content bzoj1005 zoj mil 【BZOJ1005】[HNOI2008]明明的煩惱 Description   自從明明學了樹的結構,就對奇怪的樹產生了興趣......給出標號為1到N的點,以及某些點最終的度數

數學專題(二) Burnside引理和Polya定理

Burnside引理   筆者第一次看到Burnside引理那個公式的時候一頭霧水,找了本組合數學的書一看,全是概念。後來慢慢從Polya定理開始,做了一些題總算理解了。本文將從最簡單的例子出發,解釋Burnside引理和Polya定理。然後提供一些自己做過的和上述定理相關的

bzoj1005[HNOI2008]明明的煩惱 Prufer序列+高精度

合數 ++ sizeof 數學 return prufer 情況 noi2008 con 題目描述 給出標號為1到N的點,以及某些點最終的度數,允許在任意兩點間連線,可產生多少棵度數滿足要求的樹? 輸入 第一行為N(0 < N < = 1000),接下來N

筆記篇斜率優化dp(一) HNOI2008玩具裝箱

公式 現在 getchar() 就是 clu cst 差距 直接 source 斜率優化dp 本來想直接肝這玩意的結果還是被忽悠著做了兩道數論現在整天渾渾噩噩無心學習甚至都不是太想頹廢是不是藥丸的表現各位要知道我就是故意要打刪除線並不是因為排版錯亂反正就是一個del標簽嘛

刷題BZOJ 3262 [HNOI2008]GT考試

gpo operator max markdown 一位 枚舉 return 思想 設計 Description 阿申準備報名參加GT考試,準考證號為N位數X1X2....Xn(0<=Xi<=9),他不希望準考證號上出現不吉利的數字。 他的不吉利數學A1A2..

POJ 2777Count Color

there option ati first bsp esc build AD label 【POJ 2777】Count Color Description Chosen Problem Solving and Program design as an optional

spring data jpajpa中使用count計數方法

class spa spring span pre data 根據 直接 uid spring data jpa中使用count計數方法很簡單 直接在dao層寫方法即可 int countByUidAndTenementId(String parentUid, Strin

365. Count 1 in BinaryLintCode java

body param repr rip mark ive app int solution Description Count how many 1 in binary representation of a 32-bit integer. Exampl

bzoj 1833: [ZJOI2010]count 數字計數數位dp

struct ret include opera truct mes 計數 表示 cpp 非典型數位dp 先預處理出f[i][j][k]表示從後往前第i位為j時k的個數,然後把答案轉換為ans(r)-ans(l-1),用預處理出的f數組dp出f即可(可能也不是dp吧……)

(第三場) C Shuffle Cards STL_rope || splay

rop nes struct ref lose can done this printf 題目鏈接:https://www.nowcoder.com/acm/contest/141/C 題目描述 Eddy likes to play cards game since t

HDU4372-Count the Buildings第一類Stirling數+組合數

long -c () nbsp back lin details 技術分享 color <題目鏈接> <轉載於 >>> > 題目大意: N座高樓,高度均不同且為1~N中的數,從前向後看能看到F個,從後向前看能看到B個,問有多少種可能

bzoj1009[HNOI2008]GT考試

吉利 problem 快速 www. string 直接 bsp pro click   題目傳送門:https://www.lydsy.com/JudgeOnline/problem.php?id=1009   這道題一看數據範圍:$ n<=10^9 $,顯然不是

算法LeetCode算法題-Count And Say

對象 nds iou 得到 eas for 結果 支持 amp 這是悅樂書的第153次更新,第155篇原創 01 看題和準備 今天介紹的是LeetCode算法題中Easy級別的第12題(順位題號是38)。count-and-say序列是整數序列,前五個術語如下: 1 11

主席樹+LCAp2633 (bzoj2588 Count on a tree

Description 給定一棵N個節點的樹,每個點有一個權值,對於M個詢問(u,v,k),你需要回答u xor lastans和v這兩個節點間第K小的點權。其中lastans是上一個詢問的答案,初始為0,即第一個詢問的u是明文。 Input 第一行兩個整數N,M。 第二行有N個整數,其

KMP+DPHDU - 3336 - Count the string

題目連結<http://acm.hdu.edu.cn/showproblem.php?pid=3336> 題意: 給出一個字串,求出所有前綴出現次數的和。 題解: 設方程表示內字尾能匹配到的字首個數, 因為KMP匹配到的是相同的最大字尾與最大字首,所以可以

ZOJ 1610 Count the Colors線段樹暴力

Painting some colored segments on a line, some previously painted segments may be covered by some the subsequent ones. Your task is counting the seg

演算法LeetCode演算法題-Count And Say

這是悅樂書的第153次更新,第155篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第12題(順位題號是38)。count-and-say序列是整數序列,前五個術語如下: 1 11 21 1211

快速冪bzoj 1008: [HNOI2008]越獄

1008: [HNOI2008]越獄 Description 監獄有連續編號為1…N的N個房間,每個房間關押一個犯人,有M種宗教,每個犯人可能信仰其中一種。如果 相鄰房間的犯人的宗教相同,就可能發生越獄,求有多少種狀態可能發生越獄 Input 輸入兩個整數M,N.1<=

HDU3336——Count the string擴充套件KMP

Count the string Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 14735&