算法学习笔记:莫比乌斯反演

友情链接

算法学习笔记,我实在不会(想)写,于是就。。。。
友链一份算法学习笔记:莫比乌斯反演
xxcc 大神算法学习笔记,下文大多摘自上文。
lvzelong2014 大神因为数学远远好于常人,所以不屑于写。)


luogu2522
bzoj2301


题目

2301:[HAOI2011]Problemb

TimeLimit:50Sec
MemoryLimit:256MB

Description

对于给出的 t 个询问,每次求有多少个数对(x,y),满足 axb cyd ,且 gcd(x,y)=k gcd(x,y) 函数为 x y的最大公约数。

Input

第一行一个整数 t ,接下来t行每行五个整数,分别表示 abcdk

Output

t 行,每行一个整数表示满足要求的数对(x,y)的个数

SampleInput

2
2 5 1 5 1
1 5 1 5 2

SampleOutput

14
3

HINT

100%1t500001ab500001cd500001k50000


题解

看到 gcd ,第一反应这是莫反。
然而,这题真的是莫反。
没想到我要说什么吧。
题目大意即求 i=abj=cd[gcd(i,j)=k] ,多组询问。
首先,这题不能离线,因为肯定爆空间。
那么,这题就要在线,还不可以爆时间。
接着,我们假设 f(n,m,k) 表示询问 (a=1,b=n,c=1,d=n,k=k) 的答案。
那么通过数学方法即可求得 ans=f(b,d,k)f(a1,d,k)f(b,c1,k)+f(a1,c1,k)
这样,我们就把问题转化为求 f(n,m,k)
接着,通过数学方法可以得到 f(n,m,k)=f(nk,mk,1)
这样,我们就把问题转化为求 f(n,m,1) 。即,求 i=1nj=1m[gcd(i,j)=1]
那么推导一波(设 n<m

i=1nj=1m[gcd(i,j)=1]=i=1nj=1md|id|jμ(d)=d=1nμ(d)ndmd

这样,复杂度为 O(tn)
但是,还是会爆时间。
那么,我们可以分块计算。
注意, ndmd 的取值最多只有 O(n) 种,证明见友链。
所以,我们可以预处理出 μ(i) 的前缀和,分块计算答案,复杂度 O(n)
总时间复杂度 O(tn)


总结

突然发现前面写的莫反太难懂了,就补充了这一篇,顺便推荐大佬学习笔记。
嗯。。。。看到 gcd 的题,先想容斥和莫反,再去想其他方法。。。。


标程

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=50005;
int a, b, c, d, k;
bool vis[N];
int miu[N];
int pri[N];
int sum[N];
int t, top;
void init()
{
    miu[1]=sum[1]=1;
    for (int i=2; i<=50000; i++)
    {
        if (!vis[i])
        {
            pri[++top]=i;
            miu[i]=-1;
        }
        for (int j=1; j<=top; j++)
        {
            if (i*pri[j]>50000) break;
            vis[i*pri[j]]=1;
            if (i%pri[j]==0) break;
            miu[i*pri[j]]=-miu[i];
        }
        sum[i]=sum[i-1]+miu[i];
    }
}
int calc(int x, int y)
{
    if (x>y) swap(x, y);
    int ans=0, pos=0;
    for (int i=1; i<=x; i=pos+1)
    {
        pos=min(x/(x/i), y/(y/i));
        ans+=(sum[pos]-sum[i-1])*(x/i)*(y/i);
    }
    return ans;
}
void work()
{
    scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
    a=(a-1)/k; b/=k; c=(c-1)/k; d/=k;
    int ans=calc(a, c)+calc(b, d)-calc(a, d)-calc(b, c);
    printf("%d\n", ans);
}
int main()
{
    init();
    scanf("%d", &t);
    while (t--) work();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值