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

本文介绍了一道关于莫比乌斯反演的应用题,通过解析题目的数学背景,详细阐述了如何利用莫比乌斯函数求解特定数学问题的方法,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Dragonite神犇的题。。不知道省选现场怎么样。。


我们定义F(i)i的约束和,题目要求的是

i=1nj=1mF(gcd(i,j)) mod 231,F(gcd(i,j))<=a

我们先忽略a这个限制,令g(i)表示1xn,1ym,且gcd(x,y)=i的数对(x,y)个数。
那么由莫比乌斯反演,可以得到g(i)=id,dnμ(di)ndmd
然后就有

Ans=i=1nF(i)g(i)

=i=1nF(i)id,dnμ(di)ndmd

=d=1nndmdidF(i)μ(di)

我们令f(i)=idF(i)μ(di),首先线性筛或者枚举每个数更新倍数来预处理出F(i),然后枚举每个数更新倍数,求出f(i)的前缀和,分块就好了。
考虑有了a的限制,我们发现只有F(i)<=ai才对答案有贡献,我们离线处理,按a排序,每次把合法的加入树状数组,然后可以维护出f(i)的前缀和,分块就好辣。
第一次写自然溢出,最后要and 2311
#include<iostream>
#include<cstdio>
#include<algorithm>
#define lowbit(i) (i&(-i))
using namespace std;
int N;
struct query {int n,m,a,id;} q[40005];
bool flag[100005];
int prime[100005],min_factor_a[100005],min_factor_sum[100005],mobius[100005],tree[100005],ans[40005];
pair<int,int> F[100005];
inline int read()
{
    int a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}
inline bool operator<(query a,query b)
{
    return a.a<b.a;
}
inline void add(int x,int val)
{
    for (int i=x;i<=N;i+=lowbit(i)) tree[i]+=val;
}
inline int query(int x)
{
    int tmp=0;
    for (int i=x;i;i-=lowbit(i)) tmp+=tree[i];
    return tmp;
}
inline void prepare()
{
    mobius[1]=F[1].first=F[1].second=1;
    for (int i=2;i<=N;i++)
    {
        if (!flag[i]) 
        {
            prime[++prime[0]]=i;
            mobius[i]=-1;
            min_factor_a[i]=i;
            min_factor_sum[i]=i+1;
            F[i].first=i+1;
        }
        for (int j=1;j<=prime[0]&&i*prime[j]<=N;j++)
        {
            flag[i*prime[j]]=1;
            if (i%prime[j]==0) 
            {
                mobius[i*prime[j]]=0; 
                min_factor_a[prime[j]*i]=min_factor_a[i]*prime[j];
                F[i*prime[j]].first=F[i].first/min_factor_sum[i]*(min_factor_sum[i]+min_factor_a[i*prime[j]]); 
                min_factor_sum[prime[j]*i]=min_factor_sum[i]+min_factor_a[i*prime[j]];
                break;
            }
            mobius[i*prime[j]]=-mobius[i];
            F[i*prime[j]].first=F[i].first*(prime[j]+1);
            min_factor_a[i*prime[j]]=prime[j];
            min_factor_sum[i*prime[j]]=(prime[j]+1);
        }
    }
    for (int i=1;i<=N;i++) F[i].second=i;
}
inline void solve(int x)
{
    for (int i=1,pos;i<=q[x].n;i=pos+1)
    {
        pos=min(q[x].n/(q[x].n/i),q[x].m/(q[x].m/i));
        ans[q[x].id]+=(q[x].n/i)*(q[x].m/i)*(query(pos)-query(i-1));
    }
}
int main()
{
    int testcase=read();
    for (int i=1;i<=testcase;i++)
    {
        q[i].n=read(),q[i].m=read(),q[i].a=read(),q[i].id=i;
        if (q[i].n>q[i].m) swap(q[i].n,q[i].m);
        N=max(N,q[i].n);
    }
    prepare();
    sort(q+1,q+testcase+1);
    sort(F+1,F+N+1);
    int now=0;
    for (int i=1;i<=testcase;i++)
    {
        while (now<N&&F[now+1].first<=q[i].a)
            for (int j=F[++now].second;j<=N;j+=F[now].second)
                add(j,F[now].first*mobius[j/F[now].second]);
        solve(i);
    }
    for (int i=1;i<=testcase;i++)
        printf("%d\n",ans[i]&0x7fffffff);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值