【BZOJ】3529: [Sdoi2014]数表

【题意】令F(i)为 i 的约数和,Q次询问给定n,m,a,求:ΣF(gcd(i,j))%2^31,1<=i<=n,1<=j<=m,F(gcd(i,j))<=a。n,m<=10^5,a<=10^9,Q<=20000。

【算法】数论(莫比乌斯反演)

【题解】先无视a的限制,令g(x)表示gcd(a,b)=x的数对个数,则由莫比乌斯反演定理易得

g(x) = Σx|d μ(d/x) * (n/d) * (m/d)

图片来源:PoPoQQQ课件

那么只要有最后Σi|dF(i)*μ(d/i)的前缀和,就可以用取值分块优化的方法快速求解。

F(i)可以用线性筛O(n)预处理(i%prime[j]==0时f[i*prime[j]]=f[i]+f[i/e[i]]*e[i]*prime[j],e[i]=p^a,p为i的最小素因子,a为其幂次),当然枚举倍数求更方便。

然后只需要参考YY的GCD枚举倍数贡献就可以了。

最后是F(gcd(i,j))<=a,只有F(i)<=a时有贡献。将询问离线,F(i)从小到大排序后依次计算,用树状数组维护前缀和。

复杂度O(n*(ln n)*(log n)+Q*√n*log n)

取模2^31相当于int自然溢出最后和2^31-1取与,原因大概和负数的二进制表示有关,不深究。

#include<cstdio>
#include<algorithm>
#define lowbit(x) (x&-x)
using namespace std;
const int maxn=100010,N=100000;

struct cyc{int n,m,a,id;}nn[maxn];
struct node{int num,id;}f[maxn];
int miu[maxn],prime[maxn],tot,e[maxn],c[maxn],ANS[maxn];
bool mark[maxn];
bool cmp2(node a,node b){return a.num<b.num;}
void pre(int n){
    miu[1]=f[1].num=e[1]=1;
    for(int i=2;i<=n;i++){
        if(!mark[i]){
            miu[prime[++tot]=i]=-1;
            f[i].num=i+1;
            e[i]=i;
        }
        for(int j=1;j<=tot&&i*prime[j]<=n;j++){
            mark[i*prime[j]]=1;
            if(i%prime[j]==0){
                miu[i*prime[j]]=0;
                f[i*prime[j]].num=f[i].num+f[i/e[i]].num*e[i]*prime[j];
                e[i*prime[j]]=e[i]*prime[j];
                break;
            }
            miu[i*prime[j]]=-miu[i];
            f[i*prime[j]].num=f[i].num*(prime[j]+1);
            e[i*prime[j]]=prime[j];
        }
    }
    for(int i=1;i<=N;i++)f[i].id=i;
    sort(f+1,f+N+1,cmp2);
}
bool cmp(cyc a,cyc b){return a.a<b.a;}
void modify(int x,int k){for(int i=x;i<=N;i+=lowbit(i))c[i]+=k;}
int ask(int x){int as=0;for(int i=x;i>=1;i-=lowbit(i))as+=c[i];return as;}
int main(){
    pre(N);
    int T;
    scanf("%d",&T);
    for(int i=1;i<=T;i++)scanf("%d%d%d",&nn[i].n,&nn[i].m,&nn[i].a),nn[i].id=i;
    sort(nn+1,nn+T+1,cmp);
    int pre=0;
    for(int TT=1;TT<=T;TT++){
        int n=nn[TT].n,m=nn[TT].m,a=nn[TT].a;
        while(pre+1<=N&&f[pre+1].num<=a){
            pre++;
            for(int j=f[pre].id;j<=N;j+=f[pre].id)modify(j,f[pre].num*miu[j/f[pre].id]);
        }
        int pos=0,mins=min(n,m),ans=0;
        for(int i=1;i<=mins;i=pos+1){
            pos=min(n/(n/i),m/(m/i));
            ans+=(ask(pos)-ask(i-1))*(n/i)*(m/i);
        }
        ANS[nn[TT].id]=ans&0x7fffffff;
    }
    for(int i=1;i<=T;i++)printf("%d\n",ANS[i]);
    return 0;
}
View Code

 

另一种思路,尝试用基本反演形式e=i*μ来推导。

$$ans=\sum_ {g\leq min(n,m)}F(g)\sum_{i\leq n}\sum_{j\leq m}[gcd(i,j)=g]$$

$$ans=\sum_ {g\leq min(n,m)}F(g)\sum_{d\leq min(n/g,m/g))}\mu (d)\left \lfloor n/gd \right \rfloor\left \lfloor m/gd \right \rfloor$$

(这步见Problem b

令T=gd

$$ans=\sum_{T\leq min(n,m)}\left \lfloor n/T \right \rfloor\left \lfloor m/T \right \rfloor \sum_{g|T}F(g)\mu (T/g)$$

(这步类似YY的GCD

然后后面预处理前缀和,前面√n回答询问。

posted @ 2018-01-12 13:25  ONION_CYC  阅读(260)  评论(0编辑  收藏  举报