HDU6537(2019湘潭邀请赛F)Neko and functio(min_25筛+容斥原理)
题目大意
给出一个函数 f ( n , k ) f(n,k) f(n,k)其值为选出k个大于1的数字使得有 ∑ i = 1 k a i = n ( a i > 1 ) \sum_{i=1}^ka_i=n(a_i>1) ∑i=1kai=n(ai>1)的方案数。先对其求前缀和。需要注意的是,不同的顺序,为不同的方案,如6=2*3就与6=3*2是不同的方案
解题思路
根据题目范围,可以发现简单地通过线性方法求和会超时,很自然地想到亚线性筛法。但是使用min_25筛需要目标函数为积性函数。这就需要我们先对原本地函数进行转化。据此我们可以构造出一个函数 g ( n , k ) g(n,k) g(n,k),其定义为选出k个数使得其积为n的方案数(即*f(n,k)*去掉了 a i > 1 a_i>1 ai>1条件),其为积性函数。当n为质数时, f ( n , k ) = k f(n,k)=k f(n,k)=k,当 n = p q n=p^q n=pq其中p为质数时有 f ( n , k ) = ( q + k − 1 ) ! q ! ∗ ( k − 1 ) ! f(n,k)=\frac{(q+k-1)!}{q!*(k-1)!} f(n,k)=q!∗(k−1)!(q+k−1)!(即将q个小球放入k个不同的盒子,盒子可以为空,那么就是将
q个小球和k-1个隔板一起做全排列,再消去小球的顺序和隔板的顺序),由此,就构造除了一个支持min_25筛的积性函数(当n为
p
q
p^q
pq时函数的值易求得)。则新构造出的g函数就可以方便地求出前缀和,现在需要将g函数转化回f函数,只需要将所有带1的答案减去即可,根据容斥原理可以写出推导的式子
a
n
s
=
∑
i
=
1
k
(
−
1
)
k
−
i
∗
k
!
i
!
(
k
−
i
)
!
∗
g
(
n
,
k
)
ans=\sum_{i=1}^k(-1)^{k-i}*\frac{k!}{i!(k-i)!}*g(n,k)
ans=i=1∑k(−1)k−i∗i!(k−i)!k!∗g(n,k)
即将长度小于k的组合中插入1的方案数减去
AC代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int size=5e6+5;
const int mod=1e9+7;
LL fac[65],invfac[65];
LL s,n;
bool prime[size];
int p[size];LL num[size];
LL h[size];
int tol,tot;
inline int ID(LL x){return x<=s?x:tot-n/x+1;}
LL qpow(LL a,LL b)
{
LL ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
int ks;
LL get_g(LL x,int k)
{
if(x<=1||p[k]>x)
return 0;
LL ans=ks*h[ID(x)]-(k-1)*ks;
while(1LL*p[k]*p[k]<=x&&k<=tol)
{
LL pk=p[k],t=p[k];
for(int e=1;t*pk<=x;e++,t=t*pk)
{
ans=(ans+(fac[e+ks-1]*invfac[ks-1]%mod*invfac[e]%mod)*get_g(x/t,k+1)%mod+(fac[e+1+ks-1]*invfac[ks-1]%mod*invfac[e+1]%mod))%mod;
}
k++;
}
return ans%mod;
}
void get_h()
{
for(int i=1;i<=tot;i++) h[i]=(num[i]-1+mod)%mod%mod;
int x=1;
for(int j=1;j<=tol;j++)
{
while(num[x]<p[j]*p[j]) x++;
for(int i=tot;i>=x;i--)
{
h[i]=(h[i]-(h[ID(num[i]/p[j])]-(j-1))%mod+mod)%mod;
}
}
}
int main()
{
int k;
fac[0]=1;
for(int i=1;i<65;i++) fac[i]=fac[i-1]*i%mod;
invfac[64]=qpow(fac[64],mod-2);
for(int i=63;i>=1;i--) invfac[i]=invfac[i+1]*(i+1)%mod;
invfac[0]=1;
while(~scanf("%lld%d",&n,&k))
{
tot=0;tol=0;
ks=k;
s=sqrt(n);
while(s*s<=n) s++;
while(s*s>n) s--;
for(int i=1;i<=s;i++) prime[i]=true;
for(int i=2;i<=s;i++)
{
if(prime[i]) p[++tol]=i;
for(int j=1;j<=tol&&p[j]*i<=s;j++)
{
prime[p[j]*i]=false;
if(i%p[j]==0) break;
}
}
p[tol+1]=0;
for(int i=1;i<=s;i++) num[++tot]=i;
for(int i=s;i>=1;i--) {
if(n/i>s)
{
num[++tot]=n/i;
}
}
get_h();
int sign=1;
int ans=0;
for(int i=k;i>=1;i--)
{
ks=i;
ans=(ans+sign*(((fac[k]*invfac[i]%mod)*invfac[k-i]%mod)*(get_g(n,1))%mod)+mod)%mod;
sign=-sign;
}
printf("%d\n",ans);
}
}