题解 LOJ3284 「USACO 2020 US Open Platinum」Exercise

对于一个排列,它的操作次数是它所有循环圈长度的\(\operatorname{lcm}\)

因为答案是求乘积。我们可以分别计算每个质数的贡献。

对于一个质数\(p\),它对答案的贡献是每个排列中,所有循环圈长度(分解质因数后)\(p\)的次数的最大值之和。即:

\[ans=\prod_{p}p^{\sum_{\text{一个排列}}\max_{\text{一个循环圈}i}\{len_i\text{里}p\text{的次数}\}} \]

考虑求指数部分,对\(m-1\)取模。即:

\[\sum_{\text{一个排列}}\max_{\text{一个循环圈}i}\{len_i\text{里}p\text{的次数}\} \]

考虑min-max容斥。则上式可以转化为:

\[\sum_{\text{一个排列}}\sum_{\text{循环圈的一个子集}s}(-1)^{|s|+1}\min_{i\in s}\{len_i\text{里}p\text{的次数}\} \]

最小值,可以转化为枚举一个值\(x\),若所有数都\(\geq x\),则对最小值\(+1\)。于是上式可以写成:

\[\sum_{\text{一个排列}}\sum_{\text{循环圈的一个子集}s}(-1)^{|s|+1}\sum_{x=1}^{n}\prod_{i\in s}[len_i\text{里}p\text{的次数}\geq x] \]

我们枚举质数\(p\),再枚举\(x\)。问题转化为,求所有排列的,所有循环圈的子集中,有多少个子集使得子集内所有循环圈的大小都是\(p^x\)的倍数。每个子集要带上容斥系数\((-1)^{|s|+1}\)

考虑DP。设子集内所有循环圈大小之和是\(i\cdot p^x\)的方案数为\(dp[i]\)。转移时,类似于带标号无向连通图计数的方法,我们钦定一个点为一号点。枚举一号点所在的循环圈的大小为\(j\cdot p^x\)。则可以这么转移:

\[dp[i]=\sum_{j=1}^{i}(-1)\cdot dp[i-j]\cdot {i\cdot p^x-1\choose j\cdot p^x-1}(j\cdot p^x-1)! \]

DP部分的时间复杂度是\(O(\frac{n^2}{p^{2x}})\)的。在外层枚举\(p\)\(x\)后,总复杂度是\(n^2\sum_{p}\sum_{x}\frac{1}{p^{2x}}\leq n^2\sum_{i=1}^{n}\frac{1}{i^2}\leq\frac{n^2\pi^2}{6}\),也就是\(O(n^2)\)

参考代码:

在LOJ查看

//LOJ3284
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=7500;
int n,m,fac[MAXN+5],comb[MAXN+5][MAXN+5],p[MAXN+5],cnt_p,flag[MAXN+5];
bool v[MAXN+5];
int pow_mod(int x,int i,int MOD){
	int y=1;
	while(i){
		if(i&1)y=(ll)y*x%MOD;
		x=(ll)x*x%MOD;
		i>>=1;
	}
	return y;
}
int calc(int s,int MOD){
	static int dp[MAXN+5];
	int lim=n/s;
	dp[0]=MOD-1;
	for(int i=1;i<=lim;++i){
		dp[i]=0;
		for(int j=1;j<=i;++j)if(dp[i-j]){//钦定一个一号点,一号点所在的环长为j*s
			dp[i]=(dp[i]-(ll)dp[i-j]*comb[i*s-1][j*s-1]%MOD*fac[j*s-1]%MOD+MOD)%MOD;
		}
	}
	int res=0;
	for(int i=1;i<=lim;++i)res=(res+(ll)dp[i]*comb[n][i*s]%MOD*fac[n-i*s]%MOD)%MOD;
	return res;
}
int main() {
	cin>>n>>m;
	fac[0]=1;
	for(int i=1;i<=n;++i)fac[i]=(ll)fac[i-1]*i%(m-1);//阶乘
	comb[0][0]=1;
	for(int i=1;i<=n;++i){
		comb[i][0]=1;
		for(int j=1;j<=i;++j){
			int& x=comb[i][j];
			x=comb[i-1][j]+comb[i-1][j-1];
			x=(x<(m-1)?x:x-(m-1));
		}
	}//组合数
	for(int i=2;i<=n;++i){
		if(!v[i])p[++cnt_p]=i;
		for(int j=1;j<=cnt_p&&p[j]*i<=n;++j){
			v[i*p[j]]=1;
			if(i%p[j]==0)break;
		}
	}//质数
	for(int i=1;i<=cnt_p;++i){
		for(int j=p[i];j<=n;j*=p[i]){
			flag[j]=p[i];
		}
	}//质数的幂
	
	int ans=1;
	for(int i=1;i<=n;++i)if(flag[i]){
		ans=(ll)ans*pow_mod(flag[i],calc(i,m-1),m)%m;
	}
	cout<<ans<<endl;
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/dysyn1314/p/12745877.html
今日推荐