友情链接
算法学习笔记,我实在不会(想)写,于是就。。。。
友链一份算法学习笔记:莫比乌斯反演
xxcc
大神算法学习笔记,下文大多摘自上文。
(
lvzelong2014
大神因为数学远远好于常人,所以不屑于写。)
题目
2301:[HAOI2011]Problemb
TimeLimit:50Sec
MemoryLimit:256MB
Description
对于给出的
t
个询问,每次求有多少个数对
Input
第一行一个整数
t
,接下来
Output
共
t
行,每行一个整数表示满足要求的数对
SampleInput
2
2 5 1 5 1
1 5 1 5 2
SampleOutput
14
3
HINT
100%的数据满足:1≤t≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
题解
看到
gcd
,第一反应这是莫反。
然而,这题真的是莫反。
没想到我要说什么吧。
题目大意即求
∑i=ab∑j=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(a−1,d,k)−f(b,c−1,k)+f(a−1,c−1,k)
这样,我们就把问题转化为求
f(n,m,k)
。
接着,通过数学方法可以得到
f(n,m,k)=f(⌊nk⌋,⌊mk⌋,1)
这样,我们就把问题转化为求
f(n,m,1)
。即,求
∑i=1n∑j=1m[gcd(i,j)=1]
那么推导一波(设
n<m
)
这样,复杂度为 O(tn)
但是,还是会爆时间。
那么,我们可以分块计算。
注意, ⌊nd⌋⌊md⌋ 的取值最多只有 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;
}