HDU6537(2019湘潭邀请赛F)Neko and functio(min_25筛+容斥原理)

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!(k1)!(q+k1)!(即将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=1k(1)kii!(ki)!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);
	}
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值