[莫比乌斯反演+数状数组] BZOJ3529: [Sdoi2014]数表

题意

有一张N×m的数表,其第i行第j列(1 <=i <=n,1 <=j <=m)的数值为
能同时整除i和j的所有自然数之和。给定a,计算数表中不大于a的数之和。
多次询问,输入的第一行一个整数Q表示测试点内的数据组数,接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据。
n,m,Q <=10^5

题解

设g(i)为gcd(x,y)等于i的数对个数(x<=n,y<=m),显然

g(i)=i|dμ(di)ndmd

设F(i)表示i的约数和,则答案为
i=1F(i)g(i)=i=1F(i)i|dμ(di)ndmd=d=1ndmdi|dF(i)μ(di)

这个 F(i) 可以用线性筛搞出来。
还有个条件就是F(i)<=a才对答案有贡献。
思路还是办法把 i|dF(i)μ(di) 的前缀和搞出来。
怎么满足a的约束条件呢?离线搞就行了,把询问按a排序,把F(i)的值从小到大考察,然后用树状数组实现不断的插入F(i)的贡献,维护前缀和。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define Fir first
#define Sec second
using namespace std;
const int maxn=100005, N=100000;
int Q,mu[maxn],p[maxn],ans[maxn];
bool vis[maxn];
pair<int,int> F[maxn];
struct data{
    int n,m,w,id;
    bool operator < (const data &b)const{
        return w<b.w;
    }
} q[maxn];
void get_mu(){
    memset(vis,1,sizeof(vis));
    mu[1]=1;
    for(int i=2;i<=N;i++){
        if(vis[i]) p[++p[0]]=i, mu[i]=-1;
        for(int j=1,k;j<=p[0]&&(k=p[j]*i)<=N;j++){
            vis[k]=false;
            if(i%p[j]==0){ mu[k]=0; break; }
            mu[k]=-mu[i];
        }
    }
}
int t1[maxn],t2[maxn]; // t1=(1+p1^1+p1^2+p1^3+...p1^k1)  t2=p1^k1
void get_F(){
    memset(vis,1,sizeof(vis)); p[0]=0;
    F[1].Fir=1;
    for(int i=2;i<=N;i++){
        if(vis[i]) p[++p[0]]=i, F[i].Fir=1+i, t1[i]=1+i, t2[i]=i;
        for(int j=1,k;j<=p[0]&&(k=p[j]*i)<=N;j++){
            vis[k]=false;
            if(i%p[j]==0){
                t2[k]=t2[i]*p[j]; t1[k]=t1[i]+t2[k];
                F[k].Fir=F[i].Fir/t1[i]*t1[k];
                break;
            }
            F[k].Fir=F[p[j]].Fir*F[i].Fir;
            t2[k]=p[j]; t1[k]=1+p[j];
        }
    }
    for(int i=1;i<=N;i++) F[i].Sec=i;
    sort(F+1,F+1+N);
}
int bit[maxn];
void Updata(int x,int val){
    for(;x<=N;x+=(x&(-x))) bit[x]+=val;
}
int Query(int x){
    int res=0;
    for(;x;x-=(x&(-x))) res+=bit[x];
    return res;
}
void Put(int i){
    for(int j=1;F[i].Sec*j<=N;j++) Updata(F[i].Sec*j,F[i].Fir*mu[j]);
}
int main(){
    freopen("bzoj3529.in","r",stdin);
    freopen("bzoj3529.out","w",stdout);
    get_mu(); get_F();
    scanf("%d",&Q);
    for(int i=1;i<=Q;i++) scanf("%d%d%d",&q[i].n,&q[i].m,&q[i].w), q[i].id=i;
    sort(q+1,q+1+Q);
    for(int i=1,last=0;i<=Q;i++){
        while(last<N&&F[last+1].Fir<=q[i].w) Put(++last);
        int res=0, n=q[i].n, m=q[i].m; 
        if(n>m) swap(n,m);
        for(int d=1,nxt;d<=n;d=nxt+1){
            nxt=min(n/(n/d),m/(m/d));
            res+=(Query(nxt)-Query(d-1))*(n/d)*(m/d);
        }
        ans[q[i].id]=res&0x7fffffff;
    }
    for(int i=1;i<=Q;i++) printf("%d\n",ans[i]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值