【JZOJ5224】【GDOI2018模拟7.12】C

32 篇文章 0 订阅

Description

这里写图片描述

Data Constraint

这里写图片描述

Solution

首先必须讲讲自然数幂求和。
我们设

Sk(n)=i=1nik

我们用第一类斯特林数来计算。
第一类Stirling数 s(p,k)
s(p,k)的一个的组合学解释是:将p个物体排成k个非空循环排列的方法数。
s(p,k)的递推公式: s(p,k)=(p-1)*s(p-1,k)+s(p-1,k-1) ,1<=k<=p-1
边界条件:s(p,0)=0 ,p>=1 s(p,p)=1 ,p>=0
递推关系的说明:
考虑第p个物品,p可以单独构成一个非空循环排列,这样前p-1种物品构成k-1个非空循环排列,方法数为s(p-1,k-1)。也可以前p-1种物品构成k个非空循环排列,而第p个物品插入第i个物品的左边,这有(p-1)*s(p-1,k)种方法。
由第一类斯特林数的定义可知
Cpn=Ppnp!=S(p,p)npS(p,p1)np1+S(P,0)n0p!
Cpn=Ppnp!=pi=0(1)piS(p,i)nip!
Sk(n)=j=1np!Cpji=0p1(1)piS(p,i)ji
Sp(n)=p!j=1nCpji=0p1(1)piS(p,i)j=1nji
Sp(n)=p!Cp+1n+1i=0p1(1)piS(p,i)Si(n)
Sp(n)=p!Cp+1n+1i=0p1(1)piS(p,i)j=1nji
Sp(n)=(np+1)(n+1)p+1i=0p1(1)piS(p,i)Si(n)

由于我们知道S1(n)=n*(n+1)/2,所以我们可以在O(P *P)的时间内算出Sp(n)。
接下来回归正题。
gcd(i,j)k=d=0ndki=1n/dj=1n/d[(i,j)==1]
=d=0ndk2i=1n/dϕ(i)1
前面 nd=0dk 用上面的方法求,后面的 n/di=1ϕ(i) 用杜教筛来求。不会杜教筛的请看 欧拉函数之和
时间复杂度O( N3/4 )。由于本题模的地方较多,要尽量减少取模次数,卡卡常,否则会很难过……

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const ll maxn=8e6+5,mo=1e9+7,maxn1=maxn-5;
ll phi[maxn],h[maxn][2],sk[10],s[10][10];
int d[maxn],bz[maxn];
ll n,m,i,t,j,k,l,x,y,z,ans,p;
ll hash(ll x){
    int t=x%maxn;
    while (h[t][0] && h[t][0]!=x) t=(t+1)%maxn;
    return t;
}
ll dg(ll n){
    if (n<=maxn1) return phi[n];
    ll x=hash(n),t=n%mo,k,i=2,ans=0,y=(mo+1)/2;
    if (h[x][0]) return h[x][1];h[x][0]=n;
    ans=t*(t+1)%mo*y%mo;
    while (i<=n){
        t=n/i;k=n/(n/i);
        ans=ans-(k-i+1)*dg(t)%mo;i=k+1;
    }
    h[x][1]=(ans%mo+mo)%mo;return h[x][1];
}
ll dg1(ll n){
    ll x=n%mo,y=(mo+1)/2,p,i,j,k,t;
    sk[1]=x*(x+1)%mo*y%mo;
    for (p=2;p<=m;p++){
        t=0;x=1;
        for (j=n-p+1;j<=n+1;j++)
            if (j%(p+1)==0 && !t) x=(j/(p+1))%mo*x%mo,t++; 
            else x=j%mo*x%mo;
        for (j=0;j<p;j++){
            k=((p-j)%2)?-1:1;
            x=x-k*s[p][j]*sk[j]%mo;
        }
        sk[p]=(x%mo+mo)%mo;
    }
    return sk[m];
}
int main(){
//  freopen("data.in","r",stdin);
    scanf("%lld%lld",&n,&m);phi[1]=1;
    for (i=2;i<=maxn1;i++){
        if (!bz[i]) d[++d[0]]=i,phi[i]=i-1;
        for (j=1;j<=d[0];j++){
            if (i*d[j]>maxn1) break;
            bz[i*d[j]]=1;
            if (i%d[j]==0){
                phi[i*d[j]]=phi[i]*d[j];break;
            }else phi[i*d[j]]=phi[i]*phi[d[j]];
        }
    }
    for (i=1;i<=maxn1;i++)
        phi[i]=(phi[i]+phi[i-1])%mo;
    s[0][0]=1;
    for (i=1;i<=m;i++){
        for (j=1;j<i;j++)
            s[i][j]=(s[i-1][j-1]+(i-1)*s[i-1][j])%mo;
        s[i][i]=1;
    }
    i=1;
    while (i<=n){
        x=n/i;t=n/(n/i);y=2*dg(x)%mo-1;
        ans=ans+(dg1(t)-dg1(i-1)+mo)%mo*y%mo;
        i=t+1;
    }
    ans=ans%mo;
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值