BZOJ 3529: [Sdoi2014]数表 [莫比乌斯反演 树状数组]

3529: [Sdoi2014]数表

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 1399  Solved: 694
[Submit][Status][Discuss]

Description

有一张N×m的数表,其第i行第j列(1 < =i < =礼,1 < =j < =m)的数值为
能同时整除i和j的所有自然数之和。给定a,计算数表中不大于a的数之和。

Input

输入包含多组数据。
    输入的第一行一个整数Q表示测试点内的数据组数,接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据。

Output

对每组数据,输出一行一个整数,表示答案模2^31的值。

Sample Input

2
4 4 3
10 10 5

Sample Output

20
148

HINT

1 < =N.m < =10^5  , 1 < =Q < =2×10^4

Source

Round 1 Day 1



一个位置的答案就是gcd(i,j)的约数和

一个个单独算不好优化不行,从gcd(i,j)的取值方面考虑

设F(i)为i的约数和,f(i)为gcd(x,y)=i的个数,那么答案就是ΣF(i)*f(i)

f(i)上两题用到过,反演后f(i)=Σ{i|d} miu(d/i)*(n/d)*(m/d)

d和i的取值范围相同,可以得到如下式子

现在我们只需要求出g(i)=的前缀和 这个问题就能在O(√n)的时间内出解

F(i)是约数和函数,可以通过线性筛计算,或者直接nlogn暴力枚举倍数,速度差不多

然后和上一题一样,暴力枚举每个i更新它的倍数

那么a的限制如何处理?考虑离线

我们发现对答案有贡献的i只有F(i)<=a的i 那么我们将询问按照a从小到大排序 将F(i)从小到大排序 每次询问将<=a的F(i)按照枚举倍数更新的方式插入 用树状数组来维护g的前缀和  这样枚举倍数logn,每个倍数插入树状数组logn,总共nlog^2n

本题取模有一种好厉害的做法:自然溢出int,最后&0x7FFFFFFF

复杂度O(nlog^2n+q√nlogn)

注意:排序[1,N)的话是sort(a+1,a+N),不要a+N+1.......

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=1e5+5;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1; c=getchar();}
    while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘; c=getchar();}
    return x*f;
}
struct ques{
    int n,m,a,id;
    bool operator <(const ques &r)const{return a<r.a;}
}q[N];
int n,m;
int notp[N],p[N],mu[N],minfac[N],t1[N],t2[N];
struct data{
    int s,i;
    bool operator <(const data &r)const{return s<r.s;}
}sf[N];
void sieve(){
    for(int i=1;i<N;i++) sf[i].i=i;
    mu[1]=1;
    sf[1].s=1;
    for(int i=2;i<N;i++){
        if(!notp[i]){
            p[++p[0]]=i,mu[i]=-1;
            minfac[i]=i;
            sf[i].s=i+1;
            t1[i]=i+1;
            t2[i]=i;
        }
        for(int j=1,k;j<=p[0]&&(k=i*p[j])<N;j++){
            notp[i*p[j]]=1;
            minfac[k]=p[j];
            if(i%p[j]==0){
                mu[i*p[j]]=0;
                t2[k]=t2[i]*p[j];
                t1[k]=t1[i]+t2[k];
                sf[k].s=sf[i].s/t1[i]*t1[k];
                break;
            }
            mu[i*p[j]]=-mu[i];
            t1[k]=1+p[j];
            t2[k]=p[j];
            sf[k].s=sf[i].s*sf[p[j]].s;
        }
    }
}

int g[N];
inline int lowbit(int x){return x&-x;}
inline void add(int p,int v){for(int i=p;i<N;i+=lowbit(i)) g[i]+=v;}
inline int sum(int p){
    int ret=0;
    for(int i=p;i;i-=lowbit(i)) ret+=g[i];
    return ret;
}
void ins(int x){
    for(int d=sf[x].i;d<N;d+=sf[x].i) add(d,sf[x].s*mu[d/sf[x].i]);
}
int cal(int n,int m){
    int ans=0,r=0;
    if(n>m) swap(n,m);
    for(int i=1;i<=n;i=r+1){
        r=min(n/(n/i),m/(m/i));
        ans+=(sum(r)-sum(i-1))*(n/i)*(m/i);
    }
    return ans;
}
int ans[N];
int main(int argc, const char * argv[]){
    sieve();
    sort(sf+1,sf+N);//!!!!!
    int T=read();
    for(int i=1;i<=T;i++) q[i].n=read(),q[i].m=read(),q[i].a=read(),q[i].id=i;
    sort(q+1,q+1+T);
    int now=1;
    for(int i=1;i<=T;i++){
        int a=q[i].a;
        for(;sf[now].s<=a;now++) ins(now);
        ans[q[i].id]=cal(q[i].n,q[i].m)&0x7FFFFFFF;
    }
    for(int i=1;i<=T;i++) printf("%d\n",ans[i]);
    return 0;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=1e5+5;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1; c=getchar();}
    while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘; c=getchar();}
    return x*f;
}
struct ques{
    int n,m,a,id;
    bool operator <(const ques &r)const{return a<r.a;}
}q[N];
int n,m;
int notp[N],p[N],mu[N];
struct data{
    int s,i;
    bool operator <(const data &r)const{return s<r.s;}
}sf[N];
void sieve(){
    mu[1]=1;
    for(int i=2;i<=100000;i++){
        if(!notp[i]){
            p[++p[0]]=i,mu[i]=-1;
        }
        for(int j=1,k;j<=p[0]&&(k=i*p[j])<=100000;j++){
            notp[k]=1;
            if(i%p[j]==0){
                mu[k]=0;
                break;
            }
            mu[k]=-mu[i];
        }
    }
    for(int i=1;i<=100000;i++){
        sf[i].i=i;
        for(int j=1;j*i<=100000;j++) sf[i*j].s+=i;
    }
}

int g[N];
inline int lowbit(int x){return x&-x;}
inline void add(int p,int v){for(int i=p;i<N;i+=lowbit(i)) g[i]+=v;}
inline int sum(int p){
    int ret=0;
    for(int i=p;i;i-=lowbit(i)) ret+=g[i];
    return ret;
}
inline void ins(int x){
    for(int d=sf[x].i;d<=100000;d+=sf[x].i) add(d,sf[x].s*mu[d/sf[x].i]);
}
int cal(int n,int m){
    int ans=0,r=0;
    if(n>m) swap(n,m);
    for(int i=1;i<=n;i=r+1){
        r=min(n/(n/i),m/(m/i));
        ans+=(sum(r)-sum(i-1))*(n/i)*(m/i);
    }
    return ans;
}
int ans[N];
int main(int argc, const char * argv[]){
    sieve();
    sort(sf+1,sf+100000);
    int T=read();
    for(int i=1;i<=T;i++) q[i].n=read(),q[i].m=read(),q[i].a=read(),q[i].id=i;
    sort(q+1,q+1+T);
    int now=1;
    for(int i=1;i<=T;i++){
        int a=q[i].a;
        for(;now<=100000&&sf[now].s<=a;now++) ins(now);
        ans[q[i].id]=cal(q[i].n,q[i].m)&0x7FFFFFFF;
    }
    for(int i=1;i<=T;i++) printf("%d\n",ans[i]);
    return 0;
}
时间: 2024-10-08 19:34:55

BZOJ 3529: [Sdoi2014]数表 [莫比乌斯反演 树状数组]的相关文章

bzoj 3529 [Sdoi2014]数表 (莫比乌斯反演+树状数组+离线)

题目大意:有一张$n*m$的数表,第$i$行第$j$列的数是同时能整除$i,j$的所有数之和,求数表内所有不大于A的数之和 先是看错题了...接着看对题了发现不会做了...刚了大半个下午无果 看了Po姐的题解(Orzzz)才搞懂这道题,搞清楚了莫比乌斯反演的两种经典的卷积形式的不同之处 令$\sigma(i)$表示i的约数个数和 如果去掉A这个限制,则题目是让我们求$\sum_{i=1}^{n}\sum_{j=1}^{m}\sigma(gcd(i,j))$ 考虑如何正确转化式子,让我们能够把不大

【BZOJ3529】[Sdoi2014]数表 莫比乌斯反演+树状数组

[BZOJ3529][Sdoi2014]数表 Description 有一张N×m的数表,其第i行第j列(1 < =i < =礼,1 < =j < =m)的数值为能同时整除i和j的所有自然数之和.给定a,计算数表中不大于a的数之和. Input 输入包含多组数据.    输入的第一行一个整数Q表示测试点内的数据组数,接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据. Output 对每组数据,输出一行一个整数,表示答案模2^31的值. Sample I

bzoj 3529 数表 莫比乌斯反演+树状数组

题目大意: 有一张N×m的数表,其第i行第j列(1 < =i < =礼,1 < =j < =m)的数值为能同时整除i和j的所有自然数之和.给定a,计算数表中不大于a的数之和. http://wenku.baidu.com/link?url=1zHluup-GXHdByoQXhMRwRu22Uu15y4DztIr1_hKVxjHJmuLQF4_01UQhLEOR7RJIpsGyfD_5fXrx9DE7sY6JeukaNUY83In097GjUOmZ7K ppt课件中讲的很仔细了 1

BZOJ 3529([Sdoi2014]数表-莫比乌斯反演)

有一张N×m的数表,其第i行第j列(1 < =i < =n,1 < =j < =m)的数值为能同时整除i和j的所有自然数之和.给定a,计算数表中不大于a的数之和模2^31的值模2^31的值. 1<=n.m<=10 5 ,Q<=2×10 4  组询问 记k的约数和f(k) 求∑ n i=1 ∑ m j=1 gcd(i,j)[f(gcd(i,j))≤a] #include<bits/stdc++.h> using namespace std; #defin

【BZOJ3529】【莫比乌斯反演 + 树状数组】[Sdoi2014]数表

Description 有一张N×m的数表,其第i行第j列(1 < =i < =礼,1 < =j < =m)的数值为 能同时整除i和j的所有自然数之和.给定a,计算数表中不大于a的数之和. Input 输入包含多组数据.     输入的第一行一个整数Q表示测试点内的数据组数,接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据. Output 对每组数据,输出一行一个整数,表示答案模2^31的值. Sample Input 2 4 4 3 10 10 5

【莫比乌斯反演+树状数组+分块求和】GCD Array

https://www.bnuoj.com/v3/contest_show.php?cid=9149#problem/I [题意] 给定长度为l的一个数组,初始值为0:规定了两种操作: [思路] 找到了一个讲解很清楚的博客http://www.cnblogs.com/flipped/p/HDU4947.html [Accepted] 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #inc

bzoj [SDOI2014]数表 莫比乌斯反演 BIT

bzoj [SDOI2014]数表 莫比乌斯反演 BIT 链接 bzoj luogu loj 思路 \[ \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}a*[f[gcd(i,j)]<=a] \] \[ f[]可以O(n)预处理出来 \] \[ \sum\limits_{k=1}^{n}f[k]*\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{m}[gcd(i,j)==k] \] \[ \sum\limits_{k=1}^{n}

BZOJ 3211 花神游历各国 (树状数组+并查集)

题解:首先,单点修改求区间和可以用树状数组实现,因为开平方很耗时间,所以在这个方面可以优化,我们知道,开平方开几次之后数字就会等于1 ,所以,用数组记录下一个应该开的数,每次直接跳到下一个不是1的数字进行开平方,至于这个数组,可以用并查集维护. #include <cstdio> #include <cmath> #include <iostream> using namespace std; typedef long long LL; LL c[100005]; in

●BZOJ 3529 [Sdoi2014]数表

题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3529 题解: 莫比乌斯反演. 按题目的意思,令$f(i)$表示i的所有约数的和,就是要求: $ANS=\sum f(gcd(i,j)),满足1 \leq i \leq n,1 \leq j \leq m,且 f(gcd(i,j))\leq a$ 首先 $f(i)$ 应该还是比较好推的,利用其为积性函数的特点,可以在线性筛时完成计算. 令$g[k]$表示$gcd(i,j)=k$的$(i,j)