题解:P9007 [入门赛 #9] 最澄澈的空与海 (Hard Version)
这题可不入门。
作为一个经历了高考摧残的人,最会干的事情就是化简冗长的式子。
干瞪眼看不出这两个式子有什么规律,变量太多了,因此考虑先消元。
由
通分,
因此,满足题意的
考虑引入新的参数
实话实说,这是一个非常美的式子,可惜它和上面一样,上下都含有
思路貌似到这里就断了,怎么办?当然是看题解去(bushi)。
上面是把消去了
也不用重新计算,只需要把
同样,
因此问题转化为求
为什么?
考虑
(n-1)(n-1)! 的一个因数z ,带入上式就一定可以唯一的算出一个x(z),y(z) ,x(z) 必然是一个整数。而y=z(x-n!) (由最开始的式子变形而来)在x,z 都是整数的情况下当然是整数。因此一个z 就唯一确定一组(x,y,z) ,而不同的z 确定的当然是不同的(x,y,z) 。两个问题的解构成一一映射。
根据因数个数定理,我们需要求的就是
设每个质因数出现的次数为
不能每次都重新求,考虑怎么从
我们可以快速的将
但是根号级别的分解质因数还是太慢了,能不能降低时间复杂度呢?
答案是可以的。如果我们知道 都记下来了还要试除法干嘛了),但是通过线性筛,我们可以顺便得到每个数的最小质因数是多少。每次除以最小质因数就可以大大加快算法了。
配合线性求逆元,程序跑得飞快。
还漏了一个问题,在上面那个式子中,
(z-1) 是作为分母的,因此z \not = 1 。有没有z=1 的情况呢?
int f[N],n,T,ans[N];
int prime[N],prcnt;
bool is_prime[N];
int minn_prime[N];
void get_prime(int n){
for(int i=2;i<=n;i++)
is_prime[i]=true;
for(int i=2;i<=n;i++){
if (is_prime[i]){
minn_prime[i]=i;
prime[++prcnt]=i;
}
for(int j=1;j<=prcnt;j++){
if (1ll*i*prime[j]>n) break;
is_prime[i*prime[j]]=false;
minn_prime[i*prime[j]]=prime[j];
if (i%prime[j]==0) break;
}
}
}
int ksm(int a,int b){
自己写快速幂
}
int inv[N],fac[N];
void init_inv(int n){
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=1ll*fac[i-1]*i%mod;
int tmp=ksm(fac[n],mod-2);
inv[n]=1ll*fac[n-1]*tmp%mod;
for(int i=n-1;i>=1;i--){
tmp=1ll*tmp*(i+1)%mod;
inv[i]=1ll*tmp*fac[i-1]%mod;
}
}
vector<pair<int,int> > prdiv;
void dp_init(int n){
int cur=1;
ans[0]=1;
for(int i=1;i<=n;i++){
prdiv.clear();
int tmp=i;
while (tmp!=1){
int cnt=0,div=minn_prime[tmp];
while (tmp%div==0){
tmp/=div;cnt++;
}
prdiv.push_back(make_pair(div,cnt));
}
tmp=prdiv.size();
for(int j=0;j<tmp;j++){
int val=prdiv[j].first,cnt=prdiv[j].second;
cur=1ll*cur*inv[1+f[val]]%mod;
f[val]+=(cnt<<1);
cur=1ll*cur*(1+f[val])%mod;
}
ans[i]=cur;
for(int j=0;j<tmp;j++){
int val=prdiv[j].first,cnt=prdiv[j].second;
cur=1ll*cur*inv[1+f[val]]%mod;
f[val]-=cnt;
cur=1ll*cur*(1+f[val])%mod;
}
}
}
int main(){
get_prime(1e6);
init_inv(1e6);
dp_init(1e6);
T=read();
for(int Case=1;Case<=T;Case++){
n=read();
if (n==1) printf("inf\n");
else printf("%d\n",ans[n-1]);
}
return 0;
}