题目链接:Hello 2019 D. Makoto and a Blackboard
题目大意:给你一个N,定义一个操作:将N替换为他的一个因子(包括1和N),现在重复K次以上操作,问最后期望的值是多少?
思路:显然是一道概率DP。我们先考虑对于一个素数幂,dp[i][j]表示第i次操作后变成
的概率,对其进行DP。若
不是一个素数幂,将其分解为
。在这里有一个非常重要的结论:期望是积性函数。即可分别计算
的每一个素因子的期望,根据积性函数的性质
再全部乘起来就行了。
代码如下
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
typedef long long ll;
using namespace std;
#define INF 0x3f3f3f3f
const int mod=1e9+7;
const int maxn=1e4+10;
ll n,k;
ll dp[maxn][55],inv[55];
ll solve(ll x,int c){
dp[0][c]=1;
for(int i=0;i<c;i++)dp[0][i]=0;
for(int i=1;i<=k;i++){
for(int j=0;j<=c;j++){
dp[i][j]=0;
for(int t=j;t<=c;t++)
dp[i][j]=(dp[i][j]+dp[i-1][t]*inv[t+1]%mod)%mod;
}
}
ll s1=0,s2=1;
for(int i=0;i<=c;i++){
s1=(s1+dp[k][i]*s2%mod)%mod;
s2=s2*x%mod;
}
return s1;
}
int main()
{
inv[1]=1;
for(int i=2;i<=55;i++)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
scanf("%I64d%I64d",&n,&k);
ll ans=1;
for(int i=2;1ll*i*i<=n;i++){
if(n%i==0){
int cnt=0;
while(n%i==0){
n/=i;
cnt++;
}
ans=1ll*ans*solve(i,cnt)%mod;
}
}
if(n>1)ans=1ll*ans*solve(n%mod,1)%mod;
printf("%I64d\n",ans);
return 0;
}
注意一点,在求素因子时,由于n非常大i*i<=n会爆int。
通过这道题再次理解了积性函数的一些神奇作用,还学到了一个线性求逆元的方法
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
具体证明非常容易推导。