卧薪尝胆,厚积薄发。
SHOI2006 有色图
Date: Fri Dec 14 15:45:50 CST 2018
In Category:
NoCategory
Description:
给出一个完全图和
$c$
种颜色,问有多少种本质不同(交换点后还不同)的上颜色的方式。
$1\leqslant n\leqslant 53$
Solution:
首先看上去就很像
$polya$
定理求置换群的染色方案数,根据
$polya$
定理,有:
$$
ans=\frac 1{|G|}\sum_{i=1}^{|G|} c^{m(i)}
$$
其中
$G$
是置换群,
$m(i)$
是置换的循环节个数。
但是这题却并没有那么简单,因为它的置换是点的置换,但是却是对边染色,也就是说
$m(i)$
是这个点置换对应的边置换的循环节数。考虑用循环来表示置换,设一个点置换为
$(a_1,a_2,\dots,a_{l_a})(b_1,b_2,\dots,b_{l_b})\cdots$
,考虑怎么计算它对应的边循环的循环节个数。
先计算两个循环之间的,形如:
$((a_1,b_1),(a_2,b_2),\dots)$
,这个可能包含了很多循环节,每个循环节长度是
$\text{lcm}(l_a,l_b)$
,总共有
$l_a\times l_b$
个边,因此两个循环之间的循环节个数为
$\frac{l_a\times l_b}{\text{lcm}(l_a,l_b)}=\gcd(l_a,l_b)$
。
在考虑一个点循环内部产生的边循环的个数,考虑
$l$
个点围成的一个环,由于是循环所以每次置换环转动一格,如果是奇数的话显然只有转一周才会和原来重合,每个循环节长度是
$l$
,总共有
$\frac {l(l-1)}2$
条边,因此循环节个数为
$\frac{l-1}2$
。如果是偶数的话要稍微麻烦一点,因为有两种边,第一种边循环节长度为
$\frac l 2$
,这些边有
$\frac l 2$
条,另一些边循环节长度为
$l$
,这些边有
$\frac{l(l-2)}{2}$
条,因此总循环节个数为:
$\frac{\frac l 2}{\frac l 2}+\frac{\frac{l(l-2)}{2}}{l}=1+\frac{l-2}{2}=\frac l 2$
。
所以对于一个置换,总循环节个数为:
$$
\sum_{i=1}^k\lfloor\frac{l_{i}}{2}\rfloor+\sum_{i=1}^{k-1}\sum_{j=i+1}^k\gcd(l_i,l_j)
$$
但是我们显然不能枚举所有的置换然后求解,发现实际有用的只有
$l_i$
,那我们就可以枚举一下整数划分,然后统计有多少置换的循环是这个划分,然后按照上面的式子计算就可以了。
于是问题变成了对于一个划分,有多少种置换的循环表示和他相同,由于一个长为
$l$
的循环的个数为
$(l-1)!$
(因为循环移位本质相同),那么我们就可以先枚举一个多重集排列数,然后会发现有的相同,它们之间的顺序是不应该被考虑的,于是答案就是:
$$
S=\frac{n!}{\prod l_i!\prod cnt[i]!}\times\prod (l_i-1)!=\frac{n!}{\prod l_i\prod cnt[i]!}
$$
于是我们可以暴力枚举集合划分数,然后用上面那个公式统计一下,最后除掉置换总个数
$n!$
即可。
Code:
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
typedef long long ll;
ll n,c,p;
ll ans = 0;
#define MAXN 55
ll fac[MAXN];
ll power(ll a,ll b)
{
ll res = 1;
while(b > 0)
{
if(b & 1)res = res * a % p;
a = a * a % p;
b = b >> 1;
}
return res;
}
ll cnt[MAXN];
ll gcd(ll a,ll b){return (b == 0 ? a : gcd(b,a % b));}
ll l[MAXN];
ll calc(ll x)
{
ll m = 0;
for(int i = 1;i <= x;++i)m += l[i] / 2;
for(int i = 1;i < x;++i)for(int j = i + 1;j <= x;++j)m += gcd(l[i],l[j]);
ll s = 1;
for(int i = 1;i <= x;++i)s = s * l[i] % p;
for(int i = 1;i <= n;++i)s = s * fac[cnt[i]] % p;
return power(s,p - 2) * power(c,m) % p;
}
void dfs(ll cur,ll rem,ll low)
{
if(rem == 0)
{
ans = (ans + calc(cur - 1)) % p;
return;
}
for(ll i = low;i <= rem;++i)
{
++cnt[i];l[cur] = i;
dfs(cur + 1,rem - i,i);
--cnt[i];
}
return;
}
int main()
{
scanf("%lld%lld%lld",&n,&c,&p);
fac[0] = 1;
for(int i = 1;i <= n;++i)fac[i] = fac[i - 1] * i % p;
dfs(1,n,1);
cout << ans << endl;
return 0;
}
In tag:
数学-polya定理
Copyright © 2020
wjh15101051
ღゝ◡╹)ノ♡