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

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/82314788

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

p i = 1 m j = i + 1 m g ( a F ( p i , p j ) m o d   k )

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

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

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

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

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

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

( 1 + k = 1 ( 1 ) k x k ( 3 k ± 1 ) 2 ) ( k = 0 p ( k ) x k ) = 1

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

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

直接求出 n 的所有拆分方案去统计 F ( p i , p j ) 是不现实的,考虑每对 ( p i , p j ) 出现次数 n u m [ p i ] [ p j ]
p i < p j ,假设一个拆分方案中两个数字分别有 a 个和 b 个,那么该方案中 ( p i , p j ) 出现次数就是 a b ,换个方式看这个次数就是 x p i y p j ( 1 x a , 1 y b ) 每一种方案在该方案中都出现了一次,故枚举 p i , p j 以及两个的数量 n u m i , n u m j 得到

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 ]

p i = m p j ,那么该方案中 ( p i , p i ) 出现的次数为 n u m i ( n u m i 1 ) 2 ,枚举 i n u m i ,只含有 n u m i p i 的拆分方案有
s u m [ n p i n u m i ] s u m [ n p i ( n u m i + 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 的三种形式的取值都是可以暴力预处理的,因此求出了 n u m 后我们就可以得到 F ( p i , p j ) = i 的个数 c n t [ i ] ( 0 i < k )
进而我们可以得到:

a n s = i = 0 k 1 c n t [ i ] g [ a [ i ] ]

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

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

首先若 ( x , y ) = 1
( i , x y ) = ( i , x ) ( i , y ) ( i , x y ) = 1 g c d ( i , m ) = g c d ( i , n )

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 ( p k ) ( p 是素数)的值。

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 )

其中第二步实际上是因为和 p k 不互质的数形如 k p , k [ 1 , p k 1 ] ,故 ( i , p k 1 ) ( i 1 , p k ) = 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;
}

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

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/82314788