【数论-筛法/五边形数/组合数学】BZOJ4772 显而易见的数论

【题目】
原题地址
给你一个整数 S S ,让你求所有整数划分的方案数的价值和,价值是个函数。

pi=1mj=i+1mg(aF(pi,pj)mod k) ∑ 划 分 方 案 p ∑ i = 1 m ∑ j = i + 1 m g ( a F ( p i , p j ) m o d   k )

其中 F(pi,pj)mod k F ( p i , p j ) m o d   k 是一个下标, p p 是一种划分方案的数列,同时给出:
g(n)=i=1n[(i,n)=1](i1,n) g ( n ) = ∑ i = 1 n [ ( i , n ) = 1 ] ( i − 1 , n )

p p 不同当且仅当最小表示法下的数列不同,答案模 109+7 10 9 + 7

【题目分析】
不可做,不可做。
这题真的推起来很烦。。。主要是这个 g g 打了个表发现是积性的才搞出来。

【解题思路】
以下推导大部分来自这篇博客。

这个题的三个 type t y p e 直接推看起来就很不可做,因此我们可以考虑每一个 gi g i 的贡献。

首先求一下拆分数,记 sum[i] s u m [ i ] i i 的拆分数,根据五边形数定理以及生成函数可以 O(nn) O ( n n ) 求出来,大概如下。
嗯欧拉函数的倒数就是它的生成函数,我们配合一下五边形数定理得到。

(1+k=1(1)kxk(3k±1)2)(k=0p(k)xk)=1 ( 1 + ∑ k = 1 ∞ ( − 1 ) k x k ( 3 k ± 1 ) 2 ) ( ∑ k = 0 ∞ p ( k ) x k ) = 1

考虑等式两边 xn x n 的系数, n>0 n > 0 时,右边系数为0,而左边有:
p(n)p(n1)p(n2)+p(n5)+p(n7)+=0 p ( n ) − p ( n − 1 ) − p ( n − 2 ) + p ( n − 5 ) + p ( n − 7 ) + ⋯ = 0

就是一个 O(nn) O ( n n ) 的递推式。

直接求出 n n 的所有拆分方案去统计 F(pi,pj) F ( p i , p j ) 是不现实的,考虑每对 (pi,pj) ( p i , p j ) 出现次数 num[pi][pj] n u m [ p i ] [ p j ]
pi<pj p i < p j ,假设一个拆分方案中两个数字分别有 a a 个和 b b 个,那么该方案中 (pi,pj) ( p i , p j ) 出现次数就是 ab a ∗ b ,换个方式看这个次数就是 xpiypj(1xa,1yb) 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 得到

num[pi][pj]=pinumi+pjnumjnsum[npinumi+pjnumj] n u m [ p i ] [ p j ] = ∑ p i n u m i + p j n u m j ≤ n s u m [ n − p i n u m i + p j n u m j ]

pi=mpj p i = m p j ,那么该方案中 (pi,pi) ( p i , p i ) 出现的次数为 numi(numi1)2 n u m i ( n u m i − 1 ) 2 ,枚举 inumi i 和 n u m i ,只含有 numipi n u m i 个 p i 的拆分方案有
sum[npinumi]sum[npi(numi+1)] s u m [ n − p i n u m i ] − s u m [ n − p i ( n u m i + 1 ) ]

故:
num[pi][pi]=pinuminnumi(numi1)2(sum[npinumi]sum[npi(numi+1)]) n u m [ p i ] [ p i ] = ∑ p i n u m i ≤ n n u m i ( n u m i − 1 ) 2 ( s u m [ n − p i n u m i ] − s u m [ n − p i ( n u m i + 1 ) ] )

F F 的三种形式的取值都是可以暴力预处理的,因此求出了 num n u m 后我们就可以得到 F(pi,pj)=i F ( p i , p j ) = i 的个数 cnt[i](0i<k) c n t [ i ] ( 0 ≤ i < k )
进而我们可以得到:

ans=i=0k1cnt[i]g[a[i]] a n s = ∑ i = 0 k − 1 c n t [ i ] ∗ g [ a [ i ] ]

下面的问题转化为如何在时限内得出 g g 数组在 1e7 1 e 7 范围内的取值,如果知道了我们就可以再用 O(k) O ( k ) 的复杂度得到答案。

可以猜测 g g 是一个积性函数(不然做个鬼啊qwq),然后打表发现是对的。
我们可以证明一下这个结论,下面设 (i,j)=gcd(i,j) ( i , j ) = g c d ( i , j )

首先若 (x,y)=1 ( x , y ) = 1
(i,xy)=(i,x)(i,y)(i,xy)=1gcd(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(xy) ====xyi=1[(i,xy)=](i1,xy)xyi=1[(i,x)=1][(i,y)=1](i1,x)(i1,y)xi=1[(i,x)=1](i1,x)yj=1[(j,y)=1](j1,y)g(x)g(y) g ( x y ) = ∑ i = 1 x y [ ( i , x y ) = ] ( i − 1 , x y )   = ∑ i = 1 x y [ ( i , x ) = 1 ] [ ( i , y ) = 1 ] ( i − 1 , x ) ( i − 1 , y ) = ∑ i = 1 x [ ( i , x ) = 1 ] ( i − 1 , x ) ∑ j = 1 y [ ( j , y ) = 1 ] ( j − 1 , y ) = g ( x ) g ( y )

现在我们只需要求出 g(pk) g ( p k ) ( p p 是素数)的值。

g(pk)========pki=1[(i,pk)=1](i1,pk)pki=1(i1,pk)pkj=1[(i,pk)1](i1,pk)pki=1(i1,pk)pk1pki=1(i,pk)pk1dpkdϕ(pkd)pk1ki=0piϕ(pki)pk1k1i=0pi(pkipki1)+pkpk1(k+1)(pkpk1) g ( p k ) = ∑ i = 1 p k [ ( i , p k ) = 1 ] ( i − 1 , p k ) = ∑ i = 1 p k ( i − 1 , p k ) − ∑ j = 1 p k [ ( i , p k ) ≠ 1 ] ( i − 1 , p k ) = ∑ i = 1 p k ( i − 1 , p k ) − p k − 1 = ∑ i = 1 p k ( i , p k ) − p k − 1 = ∑ d ∣ p k d ϕ ( p k d ) − p k − 1 = ∑ i = 0 k p i ϕ ( p k − i ) − p k − 1 = ∑ i = 0 k − 1 p i ( p k − i − p k − i − 1 ) + p k − p k − 1 = ( k + 1 ) ∗ ( p k − p k − 1 )

其中第二步实际上是因为和 pk p k 不互质的数形如 kp,k[1,pk1] k p , k ∈ [ 1 , p k − 1 ] ,故 (i,pk1)(i1,pk)=1 ( i , p k ≠ 1 ) ⇒ ( i − 1 , p k ) = 1 ,倒数第二步是因为 ϕ(1)=1,ϕ(pk)=pkpk1 ϕ ( 1 ) = 1 , ϕ ( p k ) = p k − p k − 1

【参考代码】

#include
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;jif(!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;iif(!bo[i]) pri[++pnum]=i,tmp[i]=i,g[i]=2*(i-1);
        for(int j=1;j<=pnum && (LL)i*pri[j]*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"%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*g[a[i]]%mod);
    printf("%d\n",ans);

    return 0;
}

【总结】
当直接推导很不可做时可以考虑每个元素的贡献。
当推导出来的函数需要它是一个积性函数时,它多半就是一个积性函数,我们可以通过打表来验证。

你可能感兴趣的:(数论-生成函数,数论-组合数学)