题目
对于一个
[
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(i−j)C(ix−1,jx−1)(jx−1)!
前面的负号是把容斥系数算进去了。
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)(n−ix)!
计算一个
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;
}
总结
- 不要什么时候都要想着可以状压……
- 拆成质因数算,这是个常见的套路。
- 善用容斥,善用“恰好”和“至少”之间的转化。