题解 SP26073 【DIVCNT1 - Counting Divisors】

· · 题解

可能更好的阅读体验

首先把式子转化为

\sum_{d=1}^n\lfloor\dfrac{n}{d}\rfloor

B=\lfloor\sqrt n\rfloor。则显然有原式等于

-B^2+2\sum_{d=1}^{B}\lfloor\dfrac{n}{d}\rfloor

考虑

\sum_{d=1}^{B}\lfloor\dfrac{n}{d}\rfloor

也就是 xy\le n,x>0,y>0,y\le B 内部的整点数,记这个区域为 R。这里贺一张著名图片。

总的思路就是用一系列 x,y 都是整数的向量拼接起来,去拟合这个函数。为了能构造出任意斜率的向量来完美拟合,我们需要 Stern-Brocot Tree

我们需要一个单调栈,里面的向量斜率单调降。设当前坐标为 (x,y)。算法流程如下:

y\le \sqrt[3] n 时可以直接暴力。可以证明这样就获得了 O(\sqrt[3]n\log n) 的优异复杂度。

还有一些细节:

代码:

#include<bits/stdc++.h>
#define ll long long
#define lll __int128
using namespace std;

void myw(lll x){
    if(!x) return;
    myw(x/10);printf("%d",(int)(x%10));
}

struct vec{
    ll x,y;
    vec (ll x0=0,ll y0=0){x=x0,y=y0;}
    vec operator +(const vec b){return vec(x+b.x,y+b.y);}
};

ll N;
vec stk[1000005];int len;
vec P;
vec L,R; 

bool ninR(vec a){return N<(lll)a.x*a.y;}
bool steep(ll x,vec a){return (lll)N*a.x<=(lll)x*x*a.y;}

lll Solve(){
    len=0;
    ll cbr=cbrt(N),sqr=sqrt(N);
    P.x=N/sqr,P.y=sqr+1;
    lll ans=0;
    stk[++len]=vec(1,0);stk[++len]=vec(1,1);
    while(1){
        L=stk[len--];
        while(ninR(vec(P.x+L.x,P.y-L.y)))
            ans+=(lll)P.x*L.y+(lll)(L.y+1)*(L.x-1)/2,
            P.x+=L.x,P.y-=L.y;
        if(P.y<=cbr) break;
        R=stk[len];
        while(!ninR(vec(P.x+R.x,P.y-R.y))) L=R,R=stk[--len];
        while(1){
            vec mid=L+R;
            if(ninR(vec(P.x+mid.x,P.y-mid.y))) R=stk[++len]=mid;
            else if(steep(P.x+mid.x,R)) break;
            else L=mid;
        }
    }
    for(int i=1;i<P.y;i++) ans+=N/i;
    return ans*2-sqr*sqr;
}

int T;

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%lld",&N);
        myw(Solve());printf("\n");
    }
}