51nod1847 奇怪的数学题

Description

http://www.51nod.com/Challenge/Problem.html#problemId=1847

计算下式

i=1nj=1nsgcd(i,j)kmod232

sgcd 函数表示第二大的 common divisor,当 gcd(i,j)=1sgcd(i,j)=0,例如 sgcd(3,5)=0,sgcd(15,10)=1

n109,k50

Solution

写这题是为了复习模板

f(x) 表示 x 的最大真因子,原式改写成

=d=1nf(d)ki=1nj=1n[gcd(i,j)=d]=d=1nf(d)k(2i=1ndφ(i)1)

后半部分是杜教筛模板,外层套整除分块即可得到,那么仍需求出 fk(i) 函数前缀和

函数本身并没有什么特别的积性,但是考察 min25 筛法的第一个 DP 的过程:依次枚举质因子并从每个 g(n,i1) 中去掉 i 在这其中的贡献

形式化的表达式写作

g[n][i]=g[n][i1](priik)×(g[nprii][i1]g[prii1][i1])

其中 g[n/pri[i]][i-1]-g[pri[i-1]][i-1] 的含义恰是将那些最大质因子为 pri[i] 的数字去掉最大质因子之后剩下的数字的 k 次幂值之和,那么可以贡献给 fk(prii)

另外由于要处理 sgcd(prii,prii) 的值,所以在 min25 筛法的过程中需要计算范围内的质数数量,这相对 trivial

min25 筛的初始状态需要赋值 g(n,0)=i=1nik,因为模数的原因不能使用依赖于逆元的 Lagrange 插值

一种可能的解决方法是出于指数很小使用第二类斯特林数,即

i=1nik=i=1nj=0kj!{kj}(ij)=j=0kj!{kj}i=1n(ij)=j=0kj!{kj}(n+1j+1)=j=0k{kj}(n+1)j+1_j+1

由于连续 j+1 个数中必然有一个 j+1 的倍数,所以找到之除掉后就没有除法操作了

Code

#define uint unsigned int
const int N=2e6+10;
int pri[N],cnt,fl[N];
map<int,uint> mp;
uint phi[N],Str[100][100],res[N],num[N],f[N],sum[N];
int R[N],tot,n,k,bl,id1[N],id2[N];
inline uint getphi(int n){
    if(n<=2e6) return phi[n];
    if(mp.count(n)) return mp[n];
    uint ans=(n&1)?(uint)(n+1)/2*n:(uint)n/2*(n+1);
    for(int l=2,r;l<=n;l=r+1){
        r=n/(n/l);
        ans-=(uint)(r-l+1)*getphi(n/l);
    }
    return mp[n]=ans;
}
inline int getid(int x){
    if(x<=bl) return id1[x];
    else return id2[n/x];
}
inline uint calc(int n){
    int pos=getid(n);
    return res[pos]+num[pos];
}
inline uint S(int n,int k){
    uint ans=0;
    rep(j,0,k){
        uint tmp=1,now=n+1;
        bool fl=0;
        rep(i,0,j){
            if(fl==0&&now%(j+1)==0) tmp*=now/(j+1),fl=1;
            else tmp*=now;
            --now;
        }
        ans+=tmp*Str[k][j];
    }
    return ans;
}
inline uint ksm(uint x,int y){
    uint res=1;
    for(;y;y>>=1,x=x*x) if(y&1) res*=x;
    return res;
}
signed main(){
    n=2e6; phi[1]=1;
    for(int i=2;i<=n;++i){
        if(!fl[i]) phi[i]=i-1,pri[++cnt]=i;
        for(int j=1;j<=cnt&&1ll*i*pri[j]<=n;++j){
            fl[i*pri[j]]=1;
            if(i%pri[j]==0){
                phi[i*pri[j]]=phi[i]*pri[j];
                break;
            }else{
                phi[i*pri[j]]=phi[i]*phi[pri[j]];
            }
        }
    }
    for(int i=1;i<=n;++i) phi[i]+=phi[i-1];
    n=read(); k=read(); bl=sqrt(n); 
    Str[1][1]=1; 
    rep(i,2,50){
        rep(j,1,i){
            Str[i][j]=Str[i-1][j]*j+Str[i-1][j-1];
        }
    }
    for(int l=1,r;l<=n;l=r+1){
        R[++tot]=n/l; r=n/R[tot];
        f[tot]=S(R[tot],k)-1;
        num[tot]=R[tot]-1;
        // Do not consider ones so subtract them
        if(R[tot]<=bl) id1[R[tot]]=tot; else id2[n/R[tot]]=tot;
    }
    for(int j=1;j<=cnt;++j){
        uint now=ksm(pri[j],k);
        sum[j]=sum[j-1]+now;
        for(int i=1;i<=tot&&1ll*pri[j]*pri[j]<=R[i];++i){
            int pos=getid(R[i]/pri[j]);
            f[i]-=(f[pos]-sum[j-1])*now;
            res[i]+=f[pos]-sum[j-1];
            num[i]-=num[pos]-(j-1); // Numbers except from primes
        }
    }
    uint ans=0;
    for(int l=1,r;l<=n;l=r+1){
        r=n/(n/l);
        ans+=(2*getphi(n/l)-1)*(calc(r)-calc(l-1));
    }
    print(ans);
    return 0;
}

代码不附带缺省源,如果需要可以去 【遇到困难睡大觉】 一文中粘贴并去掉 #define int long long

感觉最近代码越来越蓬松了,都快不可读了

posted @   没学完四大礼包不改名  阅读(105)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示