【bzoj3994】【SDOI2015】约数个数和【数论】【反演】

虽然题目上写了反演但是我不知道什么是反演……如果你把Sigma调换位置叫做反演的话。
这道题题面非常简单:
d(x) x 的约数个数,给定 NM

i=1Nj=1Md(ij)

当时我too naive,看到这玩意就默默地打50分暴力去了。。。
今天江苏神犇们做了这道题,我顺便听明白了~~
首先它不知用什么精妙的办法(可能我以后会知道),推出了
i=1Nj=1Md(ij)=i=1Nj=1MNiMi[gcd(i,j)==1]

证明办法是做二阶差分,
F(N,M)=i=1Nj=1Md(ij)
,
G(N,M)=i=1Nj=1MNiMi[gcd(i,j)==1]=i=1j=1NiMi[gcd(i,j)==1](i>NN/i0)

那么
F(N,M)F(N1,M)F(N,M1)+F(N1,M1)=d(NM)

G(N,M)G(N1,M)G(N,M1)+G(N1,M1)
=i=1j=1(NiN1i)(MiM1i)[gcd(i,j)==1]
现在我们来考虑一下G的二阶差分的意义。
(NiN1i) 只有当 i|N 的时候才为 1 (MiM1i) 同理。
即满足 1iN1jMgcd(i,j)=1i|Nj|M 的有序数对 (i,j) 的个数。由于 ij 分别是 NM 的约数,因此对于任意两个不同的质因子 pq ,它们的选择方案是互不影响的。
因此可以考虑 NM 的任一质因子 p ,设 xy 分别是满足 px|ipy|j 的最大整数。可以得出,质因子 p 在等式左边的选法数为 x+y+1 ,而在等式右边的选法数也是 x+y+1 (因为当i、j中的其中一个含有质因子p时,另一个就不可以含有质因子p)。
因此该等式成立。我们继续对该式进行变换,可以得到

F(N,M)=G(N,M)=i=1Nj=1MNiMi[gcd(i,j)==1]=i=1Nj=1MNiMid|gcd(i,j)μ(d)=d=1Nμ(d)i=1N/dj=1M/dNidMjd=d=1Nμ(d)i=1N/dNidj=1M/dMjd

g(n)=ni=1ni
则进一步化简为 Nd=1μ(d)g(Nid)g(Mjd)
可以发现g函数其实就是d函数的前缀和,于是可以线性筛出来。
关于约数个数怎么线性筛这个问题,只要记录一下每个数,他的素因子分解式中最小素因子的次数c[i]即可。

void genPrime(int n){
    memset(isprime,true,sizeof isprime);
    mu[1]=d[1]=c[1]=1;
    for(int i=2;i<=n;++i){
        if(isprime[i]){
            prime[tot++]=i;
            mu[i]=-1;
            c[i]=1;
            d[i]=2;
        }
        for(int j=0;jfalse;
            if(i%prime[j]==0){
                d[i*prime[j]]=d[i]/(c[i]+1)*(c[i]+2);
                c[i*prime[j]]=c[i]+1;break;
            }
            mu[i*prime[j]]=-mu[i];
            d[i*prime[j]]=d[i]*d[prime[j]];
            c[i*prime[j]]=1;
        }
    }
}

代码:

#include
#include
#include
using namespace std;
typedef long long ll;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
const int maxn=50001;
int tot,prime[30001],mu[maxn],c[maxn];
ll d[maxn];
bool isprime[maxn];
ll Ans[101][101];
void genPrime(int n){
    memset(isprime,true,sizeof isprime);
    mu[1]=d[1]=c[1]=1;
    for(int i=2;i<=n;++i){
        if(isprime[i]){
            prime[tot++]=i;
            mu[i]=-1;
            c[i]=1;
            d[i]=2;
        }
        for(int j=0;jfalse;
            if(i%prime[j]==0){
                d[i*prime[j]]=d[i]/(c[i]+1)*(c[i]+2);
                c[i*prime[j]]=c[i]+1;break;
            }
            mu[i*prime[j]]=-mu[i];
            d[i*prime[j]]=d[i]*d[prime[j]];
            c[i*prime[j]]=1;
        }
    }
    for(int i=1;i<=n;++i) mu[i]+=mu[i-1];
    for(int i=1;i<=n;++i) d[i]+=d[i-1];
}
inline int read(){
    int x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=x*10+ch-48,ch=getchar();
    return x;
}
char a[18];
inline void print(ll x){
    a[0]=0;
    while(x) a[++a[0]]=x%10,x/=10;
    for(;a[0];--a[0])
        putchar(a[a[0]]+48);
    putchar('\n');
}
int main(){
    genPrime(50000);
    int t=read();
    while(t--){
        int n=read(),m=read();
        if(n<=100&&m<=100){
            if(Ans[n][m]){
                print(Ans[n][m]);
                continue;
            }
        }
        if(n>m) swap(n,m);
        ll ans=0;
        for(int i=1,nex;i<=n;i=nex+1){
            nex=min(n/(n/i),m/(m/i));
            ans+=(mu[nex]-mu[i-1])*d[n/i]*d[m/i];
        }
        if(n<=100&&m<=100) Ans[n][m]=Ans[m][n]=ans;
        print(ans);
    }
    return 0;
}

你可能感兴趣的:(数论,数学基础,bzoj,sdoi)