【bzoj1004】[HNOI2008]Cards Burnside引理+背包dp

题目描述

用三种颜色染一个长度为 $n=Sr+Sb+Sg$ 序列,要求三种颜色分别有 $Sr,Sb,Sg$ 个。给出 $m$ 个置换,保证这 $m$ 个置换和置换 ${1,2,3,...,n\choose 1,2,3,...,n}$ 构成一个置换群,求置换后不同构的序列个数模 $p$ 。

$0\le Sr,Sb,Sg\le 20,0\le m\le 60,m+1\le p\le 100$ ,$p$ 是质数。

输入

第一行输入 5 个整数:Sr,Sb,Sg,m,p(m<=60,m+1<p<100)。n=Sr+Sb+Sg。
接下来 m 行,每行描述一种洗牌法,每行有 n 个用空格隔开的整数 X1X2...Xn ,恰为 1 到 n 的一个排列,表示使用这种洗牌法,第 i 位变为原来的 Xi 位的牌。输入数据保证任意多次洗牌都可用这 m 种洗牌法中的一种代替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。

输出

不同染法除以P的余数

样例输入

1 1 1 2 7
2 3 1
3 1 2

样例输出

2


题解

Burnside引理+背包dp

由于颜色有3种,因此不能直接使用Polya定理。

考虑Burnside引理推导Polya定理的过程:对于一种置换,不动点需要满足:每个循环种的颜色相同。

这种推导即可应用于本题。我们对于一个置换,取出其所有循环,这个循环需要 循环大小 个同种颜色。

显然是一个背包dp。设 $f[i][j][k]$ 表示前 $i$ 个置换,用了 $j$ 种颜色1和 $k$ 种颜色2的方案数(用了 $sum_i-j-k$ 种颜色3)。那么对于第 $i$ 个置换,讨论其颜色即可转移。

最终对于该置换的不动点数目即为 $f[k][Sr][Sb]$ ,$k$ 为循环数目。

把所有置换(包括置换后得到本身的置换 ${1,2,3,...,n\choose 1,2,3,...,n}$ )的不动点数目加起来,乘以 $m$ 的逆元即为答案。

时间复杂度 $O(mn^3)$

#include <cstdio>
#include <cstring>
int a , b , c , p , f[65][25][25] , v[65] , vis[65];
int solve()
{
	int tot = 0 , sum = 0 , w , i , j , k;
	memset(vis , 0 , sizeof(vis));
	memset(f , 0 , sizeof(f));
	f[0][0][0] = 1;
	for(i = 1 ; i <= a + b + c ; i ++ )
	{
		if(!vis[i])
		{
			tot ++ ;
			for(w = 0 , j = i ; !vis[j] ; j = v[j])
				vis[j] = 1 , w ++ ;
			sum += w;
			for(j = 0 ; j <= a ; j ++ )
			{
				for(k = 0 ; k <= b ; k ++ )
				{
					if(sum - j - k <= c)
					{
						if(j >= w) f[tot][j][k] += f[tot - 1][j - w][k];
						if(k >= w) f[tot][j][k] += f[tot - 1][j][k - w];
						if(sum - j - k >= w) f[tot][j][k] += f[tot - 1][j][k];
						f[tot][j][k] %= p;
					}
				}
			}
		}
	}
	return f[tot][a][b];
}
int main()
{
	int m , i , j , ans = 0;
	scanf("%d%d%d%d%d" , &a , &b , &c , &m , &p);
	for(i = 1 ; i <= a + b + c ; i ++ ) v[i] = i;
	ans = solve();
	for(i = 1 ; i <= m ; i ++ )
	{
		for(j = 1 ; j <= a + b + c ; j ++ ) scanf("%d" , &v[j]);
		ans = (ans + solve()) % p;
	}
	for(i = 1 ; i <= p - 2 ; i ++ ) ans = ans * (m + 1) % p;
	printf("%d\n" , ans);
	return 0;
}

我才不会告诉你们下面的代码也能过呢(数据太水了 = =)

#include <cstdio>
int p;
int pow(int x , int y)
{
	int ans = 1;
	while(y)
	{
		if(y & 1) ans = ans * x % p;
		x = x * x % p , y >>= 1;
	}
	return ans;
}
int main()
{
	int a , b , c , m , i , ans = 1;
	scanf("%d%d%d%d%d" , &a , &b , &c , &m , &p);
	for(i = 1 ; i <= a + b + c ; i ++ ) ans = ans * i % p;
	for(i = 1 ; i <= a ; i ++ ) ans = ans * pow(i , p - 2) % p;
	for(i = 1 ; i <= b ; i ++ ) ans = ans * pow(i , p - 2) % p;
	for(i = 1 ; i <= c ; i ++ ) ans = ans * pow(i , p - 2) % p;
	ans = ans * pow(m + 1 , p - 2) % p;
	printf("%d\n" , ans);
	return 0;
}

 

 

posted @ 2018-01-16 16:49  GXZlegend  阅读(296)  评论(0编辑  收藏  举报