[莫比乌斯反演] 51Nod 1584 加权约数和

这个网上找不到题解嘛 那我就来写一发!

(式子略)
不是我不想写 是我真的不会用markdown
我立一发flag 除夕我要学markdown
完成题解补完计划

Ans=2i=1nj=1iiσ(ij)i=1niσ(i2)

因为

i=1nσ(in)====i=1na|ib|nanb[(a,b)=1]b|nnba=1nnaad|(a,b)μ(d)b|nnbd|bμ(d)a=1ndnadadd|nμ(d)db|ndnbda=1ndndaa

所以

i=1nj=1iiσ(ij)====i=1nid|iμ(d)db|idibda=1ididaad=1nμ(d)di=1ndidb|iba=1iaiad=1nμ(d)d2i=1ndib|iba=1iaiad=1nμ(d)d2i=1ndisumd(i)ssumd(i)

其中有 sumd(n)=d|nd ssumd(n)=ni=1sumd(i)

其实到这一步我们就可以 O(n) 预处理 O(n) 询问了
但是复杂度高了点 刚好卡不进1s 反正我撑死1.5s左右

然后我们再推

ret=====d=1nμ(d)d2i=1ndisumd(i)ssumd(i)i=1nd|iμ(id)(id)2dsums(d)ssumd(d)i=1nd|iμ(d)d2idsums(id)ssumd(id)i=1nd|iμ(d)disums(id)ssumd(id)i=1nF(i)

这样的话 我们就可以我们就可以 O(nlnn) 预处理 O(1) 询问了

上代码
先是我打的线性预处理 根号查询
妥妥T

#include<cstdio>
#include<cstdlib>
#include<algorithm>
typedef long long ll;
using namespace std;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}

inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int P=1e9+7;
const int maxn=1000000;

int prime[200000],num;
int miu[maxn+5],sum1[maxn+5],sum2[maxn+5];
ll ssumd[maxn+5],sumd[maxn+5],t1[maxn+5],t2[maxn+5]; int d[maxn+5];
ll sumd2[maxn+5],t12[maxn+5],t22[maxn+5];
ll F[maxn+5],G[maxn+5];

inline void Pre(){
  miu[1]=1; sumd[1]=1; sumd2[1]=1;
  for (int i=2;i<=maxn;i++){
    if (!d[i]){
      d[i]=prime[++num]=i;
      miu[i]=-1;
      sumd[i]=1+i,t1[i]=1+i,t2[i]=i;
      sumd2[i]=1LL+i+(ll)i*i,t12[i]=1LL+i+(ll)i*i,t22[i]=(ll)i*i;
    }
    for (int j=1;j<=num && (ll)i*prime[j]<=maxn;j++){
      d[i*prime[j]]=prime[j]; int k=i*prime[j],p=prime[j];
      if (k==4)
      int c=1;
      if (i%prime[j]==0){
    miu[k]=0;
    t2[k]=t2[i]*p,t1[k]=t1[i]+t2[k],sumd[k]=sumd[i]/t1[i]*t1[k];
    t22[k]=t22[i]*p*p,t12[k]=t12[i]+t22[i]*((ll)p*p+p),sumd2[k]=sumd2[i]/t12[i]*t12[k];
    break;
      }
      miu[i*p]=miu[i]*miu[p];
      sumd[k]=sumd[i]*sumd[p],t1[k]=1+p,t2[k]=p;
      sumd2[k]=sumd2[i]*sumd2[p],t12[k]=1LL+p+(ll)p*p,t22[k]=(ll)p*p;
    }
  }
  for (int i=1;i<=maxn;i++){
    sum2[i]=((ll)i*i%P*((P+miu[i])%P)%P+sum2[i-1])%P;
    sum1[i]=((ll)i*((P+miu[i])%P)%P+sum1[i-1])%P;
    ssumd[i]=(ssumd[i-1]+sumd[i])%P;
    F[i]=(F[i-1]+(ll)i*(sumd[i]%P)%P*ssumd[i]%P)%P;
    G[i]=(G[i-1]+(ll)i*(sumd2[i]%P)%P)%P;
  }
}

inline int Solve(int n){
  ll ret=0; int l,r;
  for (l=1;l*l<=n;l++) ret+=(ll)(sum2[l]-sum2[l-1]+P)*F[n/l]%P;
  for (int t=n/l,r;l<=n;l=r+1,t--)
    r=n/t,ret+=(ll)(sum2[r]-sum2[l-1]+P)*F[t]%P;
  return ret%P;
}

int main(){
  int n,Q,Case=0;
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  read(Q); Pre();
  while (Q--){
    read(n);
    printf("Case #%d: %lld\n",++Case,((ll)Solve(n)*2+P-G[n])%P);
  }
  return 0;
}

然后发现根本不需要根号 nlnn预处理可以O(1)查询
然后改改就过了
我从未写线性筛写成这副模样过

#include<cstdio>
#include<cstdlib>
#include<algorithm>
typedef long long ll;
using namespace std;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}

inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int P=1e9+7;
const int maxn=1000000;

int prime[200000],num;
int miu[maxn+5];
ll mul[maxn+5];
ll ssumd[maxn+5],sumd[maxn+5],t1[maxn+5],t2[maxn+5]; int d[maxn+5];
ll sumd2[maxn+5],t12[maxn+5],t22[maxn+5];
ll F[maxn+5],G[maxn+5];

inline void Pre(){
  miu[1]=1; sumd[1]=1; sumd2[1]=1;
  for (int i=2;i<=maxn;i++){
    if (!d[i]){
      d[i]=prime[++num]=i;
      miu[i]=-1;
      sumd[i]=1+i,t1[i]=1+i,t2[i]=i;
      sumd2[i]=1LL+i+(ll)i*i,t12[i]=1LL+i+(ll)i*i,t22[i]=(ll)i*i;
    }
    for (int j=1;j<=num && (ll)i*prime[j]<=maxn;j++){
      d[i*prime[j]]=prime[j]; int k=i*prime[j],p=prime[j];
      if (k==4)
    int c=1;
      if (i%prime[j]==0){
    miu[k]=0;
    t2[k]=t2[i]*p,t1[k]=t1[i]+t2[k],sumd[k]=sumd[i]/t1[i]*t1[k];
    t22[k]=t22[i]*p*p,t12[k]=t12[i]+t22[i]*((ll)p*p+p),sumd2[k]=sumd2[i]/t12[i]*t12[k];
    break;
      }
      miu[i*p]=miu[i]*miu[p];
      sumd[k]=sumd[i]*sumd[p],t1[k]=1+p,t2[k]=p;
      sumd2[k]=sumd2[i]*sumd2[p],t12[k]=1LL+p+(ll)p*p,t22[k]=(ll)p*p;
    }
  }
  for (int i=1;i<=maxn;i++){
    ssumd[i]=(ssumd[i-1]+sumd[i])%P;
    G[i]=(G[i-1]+(ll)i*(sumd2[i]%P)%P)%P;
  }
  for (int i=1;i<=maxn;i++)
    mul[i]=ssumd[i]*sumd[i]%P;
  for (int i=1;i<=maxn;i++)
    if (miu[i])
      for (int j=i;j<=maxn;j+=i){
    if (miu[i]==1)
      F[j]+=(ll)i*j%P*mul[j/i]%P;
    else
      F[j]+=P-(ll)i*j%P*mul[j/i]%P;
    if (F[j]>=P) F[j]-=P;
      }
  for (int i=1;i<=maxn;i++){
    F[i]+=F[i-1]; if (F[i]>=P) F[i]-=P;
  }
}

int main(){
  int n,Q,Case=0;
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  read(Q); Pre();
  while (Q--){
    read(n);
    printf("Case #%d: %lld\n",++Case,((ll)F[n]*2+P-G[n])%P);
  }
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值