BZOJ3512:DZY Loves Math IV (杜教筛)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3512


题目分析:很早就听说这是道神仙题,所以一直没有做。后来洛谷的某场比赛,T1和这题很像,然而我推了1h也化不开 ϕ(ij) ϕ ( i j ) (也许我智商太低),最后在该题取得了0分的好成绩QAQ。于是我决定过来学一学关于 ϕ ϕ 的一些新姿势。

首先有以下四条式子成立:
d=gcd(i,j) d = g c d ( i , j )

d=x|i,x|jϕ(x) d = ∑ x | i , x | j ϕ ( x )

ϕ(ij)=ϕ(id)ϕ(j)d ϕ ( i j ) = ϕ ( i d ) ϕ ( j ) d

ϕ(ij)=ϕ(i)ϕ(j)dϕ(d) ϕ ( i j ) = ϕ ( i ) ϕ ( j ) d ϕ ( d )

若x无平方因子,d为x的约数,e为d的约数,则有:

ϕ(xd)ϕ(de)=ϕ(xe) ϕ ( x d ) ϕ ( d e ) = ϕ ( x e )

第一条式子其实就是 n=d|nϕ(d) n = ∑ d | n ϕ ( d ) 的变形。第二条式子中由于 id i d j j 互质,所以 ϕ(id)ϕ(j)=ϕ(ijd) ϕ ( i d ) ϕ ( j ) = ϕ ( i j d ) ,又因为d是 ijd i j d 的因数,所以等式成立。第三条式子要从 ϕ ϕ 的计算方法考虑: ϕ(n)=ipki1i(pi1) ϕ ( n ) = ∏ i p i k i − 1 ( p i − 1 ) ,意味着某个质因数 pi p i 在n中第一次出现,贡献是 pi1 p i − 1 ,再出现时贡献是 pi p i 。而i和j共有的质因子,在计算 ϕ(i)ϕ(j) ϕ ( i ) ϕ ( j ) 时乘了两次 pi1 p i − 1 ,所以要除回 ϕ(d) ϕ ( d ) ,再乘以d。第四条式子显然正确。

然而知道了上面4条式子,我们远远还不足以做出这题。由于这题解法十分脑洞,我直接开始推倒吧:

S(n,m)=mi=1ϕ(in) S ( n , m ) = ∑ i = 1 m ϕ ( i n ) ,则 ans=ni=1S(i,m) a n s = ∑ i = 1 n S ( i , m ) 。由于n比较小,可以直接枚举。考虑怎么计算 S(n,m) S ( n , m ) 。设 n=xy n = x y ,其中x是n的最大无平方因子约数,这个可以通过线性筛直接预处理。通过 ϕ ϕ 的计算方式可知 ϕ(n)=ϕ(x)y ϕ ( n ) = ϕ ( x ) y ,所以我们把y提出来:

S(n,m)=yi=1mϕ(ix) S ( n , m ) = y ∑ i = 1 m ϕ ( i x )

用第二条式子代换可得:

S(n,m)=yi=1mϕ(i)ϕ(xd)d S ( n , m ) = y ∑ i = 1 m ϕ ( i ) ϕ ( x d ) d

其中 d=gcd(x,i) d = g c d ( x , i ) 。然后用第一条式子将后面的d换掉:

S(n,m)=yi=1mϕ(i)ϕ(xd)e|x,e|iϕ(de) S ( n , m ) = y ∑ i = 1 m ϕ ( i ) ϕ ( x d ) ∑ e | x , e | i ϕ ( d e )

将e的枚举条件写成 e|x,e|i e | x , e | i 是因为 d=gcd(x,i) d = g c d ( x , i ) ,合并了d之后方便些。
然后用第四条式子将后面的两个 ϕ ϕ 合并,并交换i和e的枚举顺序,可得:

S(n,m)=ye|xϕ(xe)i=1meϕ(ie) S ( n , m ) = y ∑ e | x ϕ ( x e ) ∑ i = 1 ⌊ m e ⌋ ϕ ( i e )

所以:

S(n,m)=ye|xϕ(xe)S(e,me) S ( n , m ) = y ∑ e | x ϕ ( x e ) S ( e , ⌊ m e ⌋ )

现在这变成了递归的形式,当n=1时便无法再递归,此时要用杜教筛求出 ϕ ϕ 的前缀和。由于 mxy=mxy ⌊ ⌊ m x ⌋ y ⌋ = ⌊ m x y ⌋ ,于是递归时第二维的值始终是 mk ⌊ m k ⌋ 的形式,它只有 2m 2 m 个取值。而且这些值和杜教筛递归时求的值是一样的,所以这个式子复杂度的上界为 O(nm+n23) O ( n m + n 2 3 ) ,对S(n,m)引入记忆化可以在4s内跑过极限数据。从这里也可以看出,杜教筛时记忆化的个数远远小于 2m 2 m

这题的关键在于将y提出来,因为S(n,m)中n已经是一个定值。如果不提出y,便无法合并两个 ϕ ϕ ,就很难计算前缀和。另外,递归时如果用map进行记忆化,不要用下标符号,因为它会返回0,而原先的答案模 109+7 10 9 + 7 可能就是0,会增加时间,所以要调用count函数。


CODE:

#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

const int maxn=1000100;
const long long Mod=1000000007;
typedef long long LL;

LL phi[maxn];
LL sum[maxn];
int low[maxn];

bool vis[maxn];
int prime[maxn];
int cur=0;

#define P pair
#define MP(x,y) make_pair(x,(long long)y)

map long long> a;
map <int,long long> b;
int n,m;

void Linear_shaker()
{
    phi[1]=1;
    low[1]=1;
    for (int i=2; iif (!vis[i]) phi[i]=i-1,low[i]=i,prime[++cur]=i;
        for (int j=1; j<=cur && i*prime[j]int k=i*prime[j];
            vis[k]=true;
            if (i%prime[j])
            {
                phi[k]=phi[i]*(prime[j]-1);
                low[k]=low[i]*prime[j];
            }
            else
            {
                phi[k]=phi[i]*prime[j];
                low[k]=low[i];
                break;
            }
        }
    }
    for (int i=1; i1]+phi[i])%Mod;
}

LL Sum(LL M)
{
    return M*(M+1LL)/2LL%Mod;
}

LL Dfs(int M)
{
    if (Mreturn sum[M];
    if (b.count(M)) return b[M];
    LL temp=Sum(M),last;
    for (LL i=2; i<=M; i=last+1)
    {
        last=M/(M/i);
        temp=(temp- (last-i+1)*Dfs(M/i)%Mod +Mod)%Mod;
    }
    b.insert( MP(M,temp) );
    return temp;
}

LL Calc(int N,int M)
{
    if ( !N || !M ) return 0;
    if (N==1) return Dfs(M);
    if (M==1) return phi[N];
    if ( a.count( MP(N,M) ) ) return a[ MP(N,M) ];
    LL x=low[N],y=N/x,ue=(long long)floor( sqrt( (double)x )+0.1 ),temp=0;
    for (LL e=1; e<=ue; e++) if (x%e==0)
    {
        temp=(temp+ phi[x/e]*Calc(e,M/e)%Mod )%Mod;
        if (e!=x/e)
        {
            e=x/e;
            temp=(temp+ phi[x/e]*Calc(e,M/e)%Mod )%Mod;
            e=x/e;
        }
    }
    temp=temp*y%Mod;
    a.insert( MP( MP(N,M) ,temp) );
    return temp;
}

int main()
{
    freopen("3512.in","r",stdin);
    freopen("3512.out","w",stdout);

    Linear_shaker();
    scanf("%d%d",&n,&m);
    if (n>m) swap(n,m);
    LL ans=0;
    for (int i=n; i>=1; i--) ans=(ans+ Calc(i,m) )%Mod;
    printf("%I64d\n",ans);

    return 0;
}

你可能感兴趣的:(好题,数论)