bzoj1478 Polya定理

题目分析

可以看论文,不过还是讲讲本蒟蒻的理解吧。

这题很让人苦恼的一个地方在于,所谓循环节是点的循环,而我们要求的方案是落在边上的。于是我们先考虑点的循环如何转化为边的循环。

对于一个长度为 L L L的循环节,可以将其看作一个环,环上的边是有向边,来代表点之间的置换。以该循环节中两点为端点的边的循环节个数就是 ⌊ L 2 ⌋ \lfloor \frac{L}{2} \rfloor 2L个。

如图(为什么这么辣眼睛QAQ,总之同种颜色的边属于同一个循环节,个数为3)

图1

如果边的两个端点是两个不同的,大小分别为 L 1 L_1 L1 L 2 L_2 L2循环节,可以假装连接其中的两端点的线在不停转啊转…咳咳,总之,循环节个数为 g c d ( L 1 , L 2 ) gcd(L_1,L_2) gcd(L1,L2)。(下图循环节个数为2,对应红色和蓝色)

图2

所以我们可以暴力拆分 n n n个点被分为的循环节们长什么样。那么已经知道了循环节的组合,这样的置换有多少个呢?

假设当前拆出的点的循环节为 L 1 , L 2 , L 3 . . . L m L_1,L_2,L_3...L_m L1,L2,L3...Lm,那么将不同编号的点往这些 L L L里塞,共计 n ! L 1 ! L 2 ! . . . L m ! \frac{n!}{L_1!L_2!...L_m!} L1!L2!...Lm!n!种方式。对于每个循环,确定第一点后,后面的点排列不同,对应不同置换,所以还要乘以 ( L 1 − 1 ) ! ( L 2 − 1 ) ! . . . ( L m − 1 ) ! (L_1-1)!(L_2-1)!...(L_m-1)! (L11)!(L21)!...(Lm1)!

如果有一些 L L L相同,那么就会有方案重复,所以还要除以重复方案的排列,得到的答案就是:

n ! L 1 L 2 . . . L m k 1 ! k 2 ! . . . k t ! \frac{n!}{L_1L_2...L_m k_1!k_2!...k_t!} L1L2...Lmk1!k2!...kt!n!

其中 t t t代表共有 t t t种不同的 L L L值,每种有 k i k_i ki个。

所以循环节的贡献可以靠dfs拆分搞出来,而 ∣ G ∣ = n ! |G|=n! G=n!。带入Polya定理的公式,循环节相同的贡献统一计算即可。

代码

技巧:尽可能地预处理一切。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define ri register int
LL n,m,p,ans,gd[60][60],fac[60],L[60],K[60];
LL gcd(LL a,LL b) {return (!b)?a:gcd(b,a%b);}
LL ksm(LL x,LL y) {
	LL re=1;
	for(;y;y>>=1,x=x*x%p) if(y&1) re=re*x%p;
	return re;
}
void work(int tot) {
	LL k1=1,k2=0;
	for(ri i=1;i<=n;++i) K[i]=0;
	for(ri i=1;i<=tot;++i) ++K[L[i]];
	for(ri i=1;i<=n;++i) k1=k1*fac[K[i]]%p;
	for(ri i=1;i<=tot;++i) k1=k1*L[i]%p;
	for(ri i=1;i<=tot;++i) {
		k2+=L[i]/2;
		for(ri j=i+1;j<=tot;++j) k2+=gd[L[j]][L[i]];
	}
	ans=(ans+ksm(m,k2)*fac[n]%p*ksm(k1,p-2)%p)%p;
}
void dfs(int x,int tot,int las) {
	if(x==n) {work(tot);return;}
	for(ri i=1;i<=las&&x+i<=n;++i) L[tot+1]=i,dfs(x+i,tot+1,i);
}
int main()
{
	scanf("%lld%lld%lld",&n,&m,&p);
	for(int i=1;i<=n;++i)
		for(int j=i;j<=n;++j) gd[i][j]=gcd(i,j);
	fac[0]=1;for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%p;
	dfs(0,0,n),printf("%lld\n",ans*ksm(fac[n],p-2)%p);
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值