6545. 【GDOI2020模拟4.8】 Exercise

题目

对于一个 [ 1 , n ] [1,n] [1,n]的置换,从初始状态进行若干次直到回归初始状态的步数为它的贡献。
求所有 n ! n! n!个置换的贡献的乘积。


思考历程

显然题目可以这样转化:将 n n n个点分成若干个环,求环的 L C M LCM LCM,每种方案的成绩。
感觉跟前些天的某道题有点像,盲猜正解是跟之前一样的优美的状压DP。
然而之前那题本来就没有切……
没有想出太多的东西,直接暴力DP,用map优化。
比预估的分数高了一点点。


正解

考虑将每个质数 p p p分开计算。
考虑计算 p k p^k pk的贡献。由于恰好是 p k p^k pk不好算,所以计算至少是 p k p^k pk。设 f ( x ) f(x) f(x)表示出现了 x x x的倍数的方案数。
那么 p k p^k pk的贡献可以摊到 p 1 , p 2 , . . . , p k p^1,p^2,...,p^k p1,p2,...,pk上,所以在每个小于等于 k k k的指数处统计一次。于是答案为 ∏ p f ( p k ) \prod p^{f(p^k)} pf(pk)
现在的问题是如何计算 f ( x ) f(x) f(x)。枚举有多少长度为 x x x的环,其它的点任意选,求出方案数之后容斥一下。
g ( i ) g(i) g(i)为确定了 i x ix ix个点(这些点所在的环都是 x x x的倍数)的方案数。
转移的时候,枚举最小编号的点所在的环的大小,就可以做到没有重复和遗漏。
g ( i ) = − ∑ j = 1 i g ( i − j ) C ( i x − 1 , j x − 1 ) ( j x − 1 ) ! g(i)=-\sum_{j=1}^{i}g(i-j)C(ix-1,jx-1)(jx-1)! g(i)=j=1ig(ij)C(ix1,jx1)(jx1)!
前面的负号是把容斥系数算进去了。
f ( x ) = ∑ i = 1 n x g ( i ) C ( n , i x ) ( n − i x ) ! f(x)=\sum_{i=1}^{\frac{n}{x}}g(i)C(n,ix)(n-ix)! f(x)=i=1xng(i)C(n,ix)(nix)!
计算一个 p p p的时间复杂度是 O ( ( n p ) 2 ) O((\frac{n}{p})^2) O((pn)2),由于 ∑ 1 i 2 \sum \frac{1}{i^2} i21 O ( 1 ) O(1) O(1)的,所以总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)
这题还有个生成函数的做法。当然我听不懂……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 7510
#define ll long long
int n,mo,mo1;
int fac[N],C[N][N];
bool inp[N];
int g[N];
inline ll qpow(ll x,int y,int m=mo){
	ll res=1;
	for (;y;x=x*x%m,y>>=1)
		if (y&1)
			res=res*x%m;
	return res;
}
int main(){
//	freopen("in.txt","r",stdin);
	freopen("exercise.in","r",stdin);
	freopen("exercise.out","w",stdout);
	scanf("%d%d",&n,&mo),mo1=mo-1;
	fac[0]=1;
	for (int i=1;i<=n;++i)
		fac[i]=(ll)fac[i-1]*i%mo1;
	for (int i=0;i<=n;++i){
		C[i][0]=1;
		for (int j=1;j<=i;++j)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo1;
	}
	ll ans=1;
	for (int p=2;p<=n;++p){
		if (inp[p])
			continue;
		for (int i=p+p;i<=n;i+=p)
			inp[i]=1;
		for (int k=1,x=p;x<=n;++k,x*=p){
			g[0]=mo1-1;
			for (int i=1;i*x<=n;++i){
				ll s=0;
				for (int j=1;j<=i;++j)
					s+=(ll)g[i-j]*C[i*x-1][j*x-1]%mo1*fac[j*x-1]%mo1;
				s=(mo1-s%mo1)%mo1;
				g[i]=s;
			}
			ll f=0;
			for (int i=1;i*x<=n;++i)
				f+=(ll)g[i]*C[n][i*x]%mo1*fac[n-i*x]%mo1;
			f%=mo1;
//			printf("%d %lld\n",p,f);
			ans=ans*qpow(p,f)%mo;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

总结

  1. 不要什么时候都要想着可以状压……
  2. 拆成质因数算,这是个常见的套路。
  3. 善用容斥,善用“恰好”和“至少”之间的转化。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值