洛谷 P3312 (莫比乌斯反演+树状数组)

在这里插入图片描述
F ( x ) = ∑ d ∣ x d [ d < = a ] F(x)=\sum_{d|x}d[d<=a] F(x)=dxd[d<=a],
那么 a n s = ∑ i = 1 n ans=\sum_{i=1}^{n} ans=i=1n ∑ j = 1 m \sum_{j=1}^{m} j=1m F ( g c d ( i , j ) ) F(gcd(i,j)) F(gcd(i,j)),
那么设 g ∗ 1 = F g*1=F g1=F, g = 1 ∗ μ g=1*\mu g=1μ,带入上式,可得,
∑ t = 1 n \sum_{t=1}^{n} t=1n ∑ e ∣ t \sum_{e|t} et F ( e ) μ ( t / e ) ∗ ( n / e ) ∗ ( m / e ) F(e)\mu(t/e)*(n/e)*(m/e) F(e)μ(t/e)(n/e)(m/e),
然后再把F(x)带入,就可以很明显的发现a的值应该怎么去用了。
这个题最关键的是F的设定,如果先上来不去管a,那么到最后会发现a没有被用进去。

那么可设 f ( t ) = ∑ e ∣ t f(t)=\sum_{e|t} f(t)=et F ( e ) μ ( t / e ) F(e)\mu(t/e) F(e)μ(t/e),因为a的原因,f(t)不能直接去线性筛,
并且随着a的变化需要频繁修改,那么可以使用树状数组来维护f()的前缀和,既可以方便修改f()的值,并且能区间查询,方便整除分块。
具体见代码。

#include<bits/stdc++.h>
#define int long long 
#define ioss ios::sync_with_stdio(0),cin.tie(0)
using namespace std;
template<class cl>void read(cl& x)
{
    x=0;int f=0;char ch;ch=getchar();
    while(!isdigit(ch)){f=f|(ch=='-'),ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x=f?-x:x;
    return ;
}
template<class cl>void put(cl x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) put(x/10);
    putchar(x%10+'0');
    return;
}
//const int N=2e4+10;
const int N=1e5+10;
int tr[N];
int mu[N];
pair<int,int>f[N];// 约数和函数
int prime[N];
bitset<N>g;
int pk[N];// 辅助函数 
const int nn=1e5;
//const int mod=(1<<31);
int lowbit(int x)
{
    return x&(-x);
}
//把x处的值改为val
int add(int x,int val)
{
   for(;x<=nn;x+=lowbit(x)) tr[x]+=val;
}
int query(int x)
{
    int res=0;
    for(;x;x-=lowbit(x))
    {
        res+=tr[x];
    }
    return res;
}
struct node
{
    int n,m,a,id;
}q[20000+10];
int ans[20000+10];
int cnt=0;
const int mod=(1<<31);// -(-1<<31+1);
void fip()
{
   mu[1]=1;g[1]=g[0]=1; 
   f[1]={1,1};
   for(int i=2;i<=nn;i++)
   {
      if(g[i]==0)
      {
          prime[++cnt]=i;
          mu[i]=-1;
          pk[i]=i+1;
          f[i]={i+1,i};
      }
      for(int j=1;j<=cnt&&prime[j]*i<=nn;j++)
      {
          g[i*prime[j]]=1;
          if(i%prime[j]==0)
          {
             pk[i*prime[j]]=pk[i]*prime[j]+1;
             f[i*prime[j]]={f[i].first/pk[i]*pk[i*prime[j]],i*prime[j]};
             break ; // 找了半天,少写一个break 靠。
          }
          mu[i*prime[j]]=-mu[i];
          //pk[i*prime[j]]=pk[i]*pk[prime[j]];
           pk[i*prime[j]]=prime[j]+1;
          f[i*prime[j]]={f[i].first*f[prime[j]].first,i*prime[j]};
      }
   }
   sort(f+1,f+1+nn);
}
bool cmp(node ss, node s)
{
    return ss.a<s.a;
}
int sum(int n,int m)
{
    int ans=0;
    if(n>m) swap(n,m);
   for(int l=1,r;l<=n;l=r+1)
   {
       r=min(n/(n/l),m/(m/l));
       ans=ans+(m/l)*(n/l)*(query(r)-query(l-1));
   }
   return ans;
}
void to()
{
    int qq;
    read(qq);
    for(int i=1;i<=qq;i++)
    {
        read(q[i].n);
        read(q[i].m);
        read(q[i].a);
        q[i].id=i;
    }
    sort(q+1,q+1+qq,cmp);
    int j=1;
    for(int i=1;i<=qq;i++)// a从小到大,这样可以避免频繁修改 将之前加上的值累积起来。
    {
      while(f[j].first<=q[i].a&&j<=nn)
      {
          for(int k=f[j].second;k<=nn;k=k+f[j].second) // first 与 second 
          {
              add(k,f[j].first*mu[k/f[j].second]);
              // 容易写成k/f[j].first 
          }
          j++;
      }
      ans[q[i].id]=sum(q[i].n,q[i].m);
    }
    for(int i=1;i<=qq;i++)
    {
        put(ans[i]%mod);
        //如果是模一个二的整数幂,可以最后再进行取模,中间的取模可以略去 也可以&2^n-1.
        puts("");
    }
    return ;
}
// int 1<<31 溢出 -1<<31  ~1 = -(1+1)
// 对于mod 2的次方 可以到最后再进行mod 2^n 。
signed main()
{
    
   fip();//cout<<f[6].first<<endl;
  // cout<<f[6].second<<endl;
  /* for(int i=1;i<=10;i++)
   {
       cout<<f[i].first<<" ";
   }*/
  // cout<<mu[15]<<endl;
   to();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值