【题目】
原题地址
给你一个整数
S
S
,让你求所有整数划分的方案数的价值和,价值是个函数。
其中 F(pi,pj)mod k F ( p i , p j ) m o d k 是一个下标, p p 是一种划分方案的数列,同时给出:
p p 不同当且仅当最小表示法下的数列不同,答案模
【题目分析】
不可做,不可做。
这题真的推起来很烦。。。主要是这个
g
g
打了个表发现是积性的才搞出来。
【解题思路】
以下推导大部分来自这篇博客。
这个题的三个直接推看起来就很不可做,因此我们可以考虑每一个 gi g i 的贡献。
首先求一下拆分数,记
sum[i]
s
u
m
[
i
]
为
i
i
的拆分数,根据五边形数定理以及生成函数可以求出来,大概如下。
嗯欧拉函数的倒数就是它的生成函数,我们配合一下五边形数定理得到。
考虑等式两边 xn x n 的系数, n>0 n > 0 时,右边系数为0,而左边有:
就是一个 O(nn−−√) O ( n n ) 的递推式。
直接求出
n
n
的所有拆分方案去统计是不现实的,考虑每对
(pi,pj)
(
p
i
,
p
j
)
出现次数
num[pi][pj]
n
u
m
[
p
i
]
[
p
j
]
。
若
pi<pj
p
i
<
p
j
,假设一个拆分方案中两个数字分别有
a
a
个和个,那么该方案中
(pi,pj)
(
p
i
,
p
j
)
出现次数就是
a∗b
a
∗
b
,换个方式看这个次数就是
x个pi和y个pj(1≤x≤a,1≤y≤b)
x
个
p
i
和
y
个
p
j
(
1
≤
x
≤
a
,
1
≤
y
≤
b
)
每一种方案在该方案中都出现了一次,故枚举
pi,pj
p
i
,
p
j
以及两个的数量
numi,numj
n
u
m
i
,
n
u
m
j
得到
若 pi=mpj p i = m p j ,那么该方案中 (pi,pi) ( p i , p i ) 出现的次数为 numi(numi−1)2 n u m i ( n u m i − 1 ) 2 ,枚举 i和numi i 和 n u m i ,只含有 numi个pi n u m i 个 p i 的拆分方案有
故:
F
F
的三种形式的取值都是可以暴力预处理的,因此求出了后我们就可以得到
F(pi,pj)=i
F
(
p
i
,
p
j
)
=
i
的个数
cnt[i](0≤i<k)
c
n
t
[
i
]
(
0
≤
i
<
k
)
进而我们可以得到:
下面的问题转化为如何在时限内得出 g g 数组在范围内的取值,如果知道了我们就可以再用 O(k) O ( k ) 的复杂度得到答案。
可以猜测
g
g
是一个积性函数(不然做个鬼啊qwq),然后打表发现是对的。
我们可以证明一下这个结论,下面设
首先若
(x,y)=1
(
x
,
y
)
=
1
则
(i,xy)=(i,x)∗(i,y),(i,xy)=1⇒gcd(i,m)=gcd(i,n)
(
i
,
x
y
)
=
(
i
,
x
)
∗
(
i
,
y
)
,
(
i
,
x
y
)
=
1
⇒
g
c
d
(
i
,
m
)
=
g
c
d
(
i
,
n
)
现在我们只需要求出
g(pk)
g
(
p
k
)
(
p
p
是素数)的值。
其中第二步实际上是因为和 pk p k 不互质的数形如 kp,k∈[1,pk−1] k p , k ∈ [ 1 , p k − 1 ] ,故 (i,pk≠1)⇒(i−1,pk)=1 ( i , p k ≠ 1 ) ⇒ ( i − 1 , p k ) = 1 ,倒数第二步是因为 ϕ(1)=1,ϕ(pk)=pk−pk−1 ϕ ( 1 ) = 1 , ϕ ( p k ) = p k − p k − 1
【参考代码】
#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;
}
【总结】
当直接推导很不可做时可以考虑每个元素的贡献。
当推导出来的函数需要它是一个积性函数时,它多半就是一个积性函数,我们可以通过打表来验证。