【题目】
原题地址
给你一个整数
,让你求所有整数划分的方案数的价值和,价值是个函数。
其中 是一个下标, 是一种划分方案的数列,同时给出:
不同当且仅当最小表示法下的数列不同,答案模
【题目分析】
不可做,不可做。
这题真的推起来很烦。。。主要是这个
打了个表发现是积性的才搞出来。
【解题思路】
以下推导大部分来自这篇博客。
这个题的三个 直接推看起来就很不可做,因此我们可以考虑每一个 的贡献。
首先求一下拆分数,记
为
的拆分数,根据五边形数定理以及生成函数可以
求出来,大概如下。
嗯欧拉函数的倒数就是它的生成函数,我们配合一下五边形数定理得到。
考虑等式两边 的系数, 时,右边系数为0,而左边有:
就是一个 的递推式。
直接求出
的所有拆分方案去统计
是不现实的,考虑每对
出现次数
。
若
,假设一个拆分方案中两个数字分别有
个和
个,那么该方案中
出现次数就是
,换个方式看这个次数就是
每一种方案在该方案中都出现了一次,故枚举
以及两个的数量
得到
若 ,那么该方案中 出现的次数为 ,枚举 ,只含有 的拆分方案有
故:
的三种形式的取值都是可以暴力预处理的,因此求出了
后我们就可以得到
的个数
进而我们可以得到:
下面的问题转化为如何在时限内得出 数组在 范围内的取值,如果知道了我们就可以再用 的复杂度得到答案。
可以猜测
是一个积性函数(不然做个鬼啊qwq),然后打表发现是对的。
我们可以证明一下这个结论,下面设
首先若
则
现在我们只需要求出
(
是素数)的值。
其中第二步实际上是因为和 不互质的数形如 ,故 ,倒数第二步是因为
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2005,M=1e7+5,mod=1e9+7;
int typ,n,K,pnum,ans;
int pri[664600],tmp[M],cnt[M],g[M],a[M],bo[M];
int sum[N],p[N][N],gcd[N][N],num[N][N];
void up(int &x,int y) {x+=y;if(x>=mod)x-=mod;if(x<0)x+=mod;}
void init()
{
for(int i=1;i<=n;++i)
{
p[i][0]=1;
for(int j=1;j<=n;++j) p[i][j]=(LL)p[i][j-1]*i%K;
}
for(int i=1;i<=n;++i)
gcd[0][i]=gcd[i][0]=gcd[i][i]=i,gcd[i][1]=gcd[1][i]=1;
for(int i=2;i<=n;++i)
for(int j=2;j<i;++j)
{
if(!gcd[i][j]) gcd[i][j]=gcd[j][i-j];
gcd[j][i]=gcd[i][j];
}
sum[0]=1;
for(int i=1;i<=n;++i)
{
for(int j=1,k=1;k<=i;k+=3*j+1,++j)
up(sum[i],(LL)sum[i-k]*(j&1?1:-1));
for(int j=1,k=2;k<=i;k+=3*j+2,++j)
up(sum[i],(LL)sum[i-k]*(j&1?1:-1));
}
g[0]=0;g[1]=1;
for(int i=2;i<M;++i)
{
if(!bo[i]) pri[++pnum]=i,tmp[i]=i,g[i]=2*(i-1);
for(int j=1;j<=pnum && (LL)i*pri[j]<M;++j)
{
bo[i*pri[j]]=1;
if(!(i%pri[j]))
{
tmp[i*pri[j]]=tmp[i]*pri[j];
if(tmp[i]^i) g[i*pri[j]]=(LL)g[i/tmp[i]]*g[tmp[i]*pri[j]]%mod;
else g[i*pri[j]]=((LL)pri[j]*g[i]+i*pri[j]-i)%mod;
break;
}
tmp[i*pri[j]]=pri[j];
g[i*pri[j]]=(LL)g[i]*g[pri[j]]%mod;
}
}
}
int f(int x,int y)
{
if(typ==1) return 1%K;
if(typ==2) return gcd[x][y]%K;
return (p[x][y]+p[y][x]+(x^y))%K;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ4772.in","r",stdin);
freopen("BZOJ4772.out","w",stdout);
#endif
scanf("%d%d%d",&typ,&n,&K);
for(int i=0;i<K;++i) scanf("%d",&a[i]);
init();
for(int i=1;i<=n;++i)
for(int j=i+1;i+j<=n;++j)
{
int t=f(i,j);
for(int ni=1;ni*i+j<=n;++ni)
for(int nj=1;ni*i+nj*j<=n;++nj)
up(cnt[t],sum[n-ni*i-nj*j]);
}
for(int i=1;i<=n;++i)
{
int t=f(i,i);
for(int ni=1;ni*i<=n;++ni)
{
int s=sum[n-ni*i];
if((ni+1)*i<=n) up(s,-sum[n-(ni+1)*i]);
up(cnt[t],(LL)ni*(ni-1)/2*s%mod);
}
}
for(int i=0;i<K;++i) up(ans,(LL)cnt[i]*g[a[i]]%mod);
printf("%d\n",ans);
return 0;
}
【总结】
当直接推导很不可做时可以考虑每个元素的贡献。
当推导出来的函数需要它是一个积性函数时,它多半就是一个积性函数,我们可以通过打表来验证。