莫比乌斯反演
现有 \(f(n),g(n)\) 两个数论函数。
如果有
那么可以得出
「HAOI 2011」Problem b
给出 \(x,y,n,m,k\) 。求
整除分块即可。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int MAXN = 1e5 + 5;
bool vis[MAXN];
int len, mu[MAXN], prime[MAXN];
void Getmu() {
mu[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = i, mu[i] = - 1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = - mu[i];
}
}
for (int i = 2; i < MAXN; i++) mu[i] += mu[i - 1];
}
int T,n,m,x,y,k;
LL calc(int n,int m){
LL ans=0;
for(int i=1,j;i<=min(n/k,m/k);i=j+1){
j=min(n/(n/i),m/(m/i));
ans+=(mu[j]-mu[i-1])*(n/(i*k))*(m/(i*k));
}
return ans;
}
int main() {
Getmu();
int T;
scanf("%d",&T);
while(T--){
scanf("%d %d %d %d %d",&n,&m,&x,&y,&k);
printf("%d\n",calc(m,y)-calc(m,x-1)-calc(n-1,y)+calc(n-1,x-1));
}
return 0;
}
gcd求和¶
莫比乌斯反演入门好题 \(QAQ\) 。
\(T\) 次询问,每次询问给出 \(n,m\) 。求
然后再枚举一下 \(\gcd(i,j)\) ,并把 \(d\) 放进里面去。
其实在这里已经可以做了,但是每次询问是 \(\mathcal{O(n+m)}\) ,于是尝试优化成 \(\mathcal{O(\sqrt{\min(n,m)})}\) 。
但是这个式子不好优化,考虑枚举 \(dD\) ,直接设为 \(T\) 。
发现 \(\sum_{d|T}d\cdot\mu(\dfrac{T}{d})\) 这一堆就是 \(\varphi(T)\) (上面列出了这个式子的)。
所以可以推出
直接整除分块即可,至此我们得到了 \(\mathcal{O(n)}\) 预处理,每次询问 \(\mathcal{O(\sqrt{\min(n,m)})}\) 的算法。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 19, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 19], *p1 = buf, *p2 = buf;
LL read() {
LL ret = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
for (; isdigit(ch); ch = getchar()) ret = ret * 10 + ch - 48;
return ret * f;
}
const int MAXN = 1e7 + 5;
bool vis[MAXN];
LL n, m, len, phi[MAXN], prime[MAXN], f[MAXN], low[MAXN], cnt[MAXN];
void seive1() {
phi[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = i, phi[i] = i - 1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
for (int i = 1; i < MAXN; i++) phi[i] += phi[i - 1];
}
void seive2() {
low[1] = f[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = low[i] = i, f[i] = 2 * i - 1, cnt[i] = 1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
low[i * prime[j]] = low[i] * prime[j];
if (low[i] == i)
cnt[i * prime[j]] = cnt[i] + 1,
f[i * prime[j]] = (cnt[i * prime[j]] + 1) * i * prime[j] - cnt[i * prime[j]] * i;
else
f[i * prime[j]] = f[i / low[i]] * f[low[i] * prime[j]];
break;
}
low[i * prime[j]] = prime[j];
f[i * prime[j]] = f[i] * f[prime[j]];
}
}
for (int i = 2; i < MAXN; i++) f[i] = 2 * f[i] - i, f[i] += f[i - 1];
}
LL calc(LL n, LL m) {
LL ans = 0, l, r;
for (l = 1; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (phi[r] - phi[l - 1]) * (n / l) * (m / l);
}
return ans;
}
int type, t;
int main() {
type = read(), t = read();
if (type >= 4 && type <= 7) {
seive2();
while (t--) {
n = read(), m = read();
printf("%lld\n", f[n]);
}
} else {
seive1();
while (t--) {
n = read(), m = read();
printf("%lld\n", calc(n, m));
}
}
return 0;
}
「BZOJ 2154」Crash 的数字表格¶
给出 \(n,m\) 。求
首先,肯定要把 \(\operatorname{lcm(i,j)}\) 搞掉。
这里解释一下为什么后面还会多个 \(d^2\) ,因为你在砍掉 \(d\) 这个因子的时候, \(ij\) 的贡献就被砍掉了 \(d^2\) ,所以要补上。
后面那一坨很恶心,把后面那一坨搞出来。
设
来一波反演。
感觉还是不是很好做。于是决定改变枚举的对象
把 \(d^2\) 搞出来。
发现后面就是一个等差数列求和。
于是 \(f(n,m)\) 就解决了。重新回到上面来。
发现 \(f(n,m)\) 可以数论分块,于是数论分块套数论分块即可,但是这无法支持多次询问,一次询问就是 \(\mathcal{O(n+m)}\) 。我写的代码有点卡常数。
不开 \(\operatorname{long long}\) 见祖宗!
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int MAXN = 1e7 + 5;
LL mod = 20101009;
bool vis[MAXN];
int len, mu[MAXN], prime[MAXN];
LL s[MAXN];
void Getmu() {
mu[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = i, mu[i] = -1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
}
LL Calc(int n, int m) {
LL ans = 0;
for (LL i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
ans += ((s[j] - s[i - 1]) % mod) *
(((1 + n / i) * (n / i) / 2 % mod) * ((1 + m / i) * (m / i) / 2 % mod) % mod) % mod;
ans %= mod;
}
return ans;
}
LL calc(int n, int m) {
LL ans = 0;
for (LL i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
ans += (((j - i + 1) * (i + j) / 2 % mod) * Calc(n / i, m / i)) % mod;
}
return ans;
}
int n, m;
int main() {
Getmu();
scanf("%d %d", &n, &m);
for (LL i = 1; i < MAXN; i++) s[i] = s[i - 1] + (i * i * mu[i] % mod), s[i] %= mod;
printf("%lld\n", (calc(n, m) % mod + mod) % mod);
return 0;
}
如果我们想要的得到更优的算法,怎么办呢?
想到可以跟上面的 \(\operatorname{gcd}\) 求和 一样改变枚举对象。
于是枚举一个 \(T=Dd\) 。
发现 \(\sum_{d|T}\mu(d)dT\) 是一个积性函数,为什么呢?
这里补充一下积性函数的知识。
如果两个函数 \(f(x)\) 与 \(g(x)\) 都是积性函数,那么一下函数都是积性函数:
可以发现 \(\sum_{d|T}\mu(d)dT\) 满足第四条。于是可以直接线性筛,然后对 \(T\) 数论分块即可。
单次询问时间复杂度 \(\mathcal{O(\sqrt{\min(n,m)})}\) 。
设 \(q(x)=\sum_{d|x}\mu(d)dT\) ,\(w(x)=\sum_{d|x}\mu(d)d\) 。
可以得到
然后线性筛 \(q(x)\) 即可。
代码就不放了。
「SDOI2015」约数个数和
给出 \(n,m\) 。求
其 \(d(x)\) 表示数 \(x\) 的约数个数。
这个题很妙啊, \(d(ij)\) 有一个特殊性质。
代进原式可得
跟原来一样的套路,莫比乌斯反演一下。
但是这样并不好做,考虑先交换和式顺序。
后面那两坨形式是一样的,直接整除分块预处理即可,然后对 \(d\) 整除分块即可。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int MAXN = 5e4 + 5;
LL mod = 20101009;
bool vis[MAXN];
int len, prime[MAXN];
LL s[MAXN], mu[MAXN];
void Getmu() {
mu[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = i, mu[i] = -1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for (int i = 2; i < MAXN; i++) mu[i] += mu[i - 1];
for (int k = 1; k < MAXN; k++) {
for (int i = 1, j; i <= k; i = j + 1) {
j = k / (k / i);
s[k] += (j - i + 1) * (k / i);
}
}
}
LL calc(LL n, LL m) {
LL ans = 0;
for (LL i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
ans += (mu[j] - mu[i - 1]) * s[n / i] * s[m / i];
}
return ans;
}
LL n, m;
signed main() {
int T;
scanf("%d", &T);
Getmu();
while (T--) {
scanf("%lld %lld", &n, &m);
printf("%lld\n", calc(n, m));
}
return 0;
}
「LuoGu P2257」YY的GCD
\(T\) 次询问,每次询问给出 \(n,m\) 。求
老套路,改变枚举对象,枚举 \(T=dD\) 。
题目给了 \(4s\) ,后面那一坨直接预处理即可。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int MAXN = 1e7 + 5;
bool vis[MAXN];
int len, prime[MAXN];
LL s[MAXN], mu[MAXN];
void Getmu() {
mu[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = i, mu[i] = -1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for (int i = 1; i <= len; i++)
for (int j = 1; j * prime[i] <= MAXN - 5; j++) s[j * prime[i]] += mu[j];
for (int i = 1; i <= MAXN - 5; i++) s[i] += s[i - 1];
}
LL calc(int n, int m) {
LL ans = 0;
int mmp = min(n, m);
for (LL i = 1, j; i <= mmp; i = j + 1) {
LL mmp1 = n / i, mmp2 = m / i;
j = min(n / mmp1, m / mmp2);
ans += (s[j] - s[i - 1]) * mmp1 * mmp2;
}
return ans;
}
int n, m;
signed main() {
int T;
scanf("%d", &T);
Getmu();
while (T--) {
scanf("%d %d", &n, &m);
printf("%lld\n", calc(n, m));
}
return 0;
}
「NOI 2010」能量采集
好水啊 \(QAQ\) 。
给出 \(n,m\) 。求
把上面的 \(\operatorname{gcd}\) 求和套一下就好了。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 19, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 19], *p1 = buf, *p2 = buf;
LL read() {
LL ret = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
for (; isdigit(ch); ch = getchar()) ret = ret * 10 + ch - 48;
return ret * f;
}
const int MAXN = 1e7 + 5;
bool vis[MAXN];
LL n, m, len, phi[MAXN], prime[MAXN], f[MAXN], low[MAXN], cnt[MAXN];
void seive1() {
phi[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = i, phi[i] = i - 1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
for (int i = 1; i < MAXN; i++) phi[i] += phi[i - 1];
}
void seive2() {
low[1] = f[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i])
prime[++len] = low[i] = i, f[i] = 2 * i - 1, cnt[i] = 1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
low[i * prime[j]] = low[i] * prime[j];
if (low[i] == i)
cnt[i * prime[j]] = cnt[i] + 1,
f[i * prime[j]] = (cnt[i * prime[j]] + 1) * i * prime[j] - cnt[i * prime[j]] * i;
else
f[i * prime[j]] = f[i / low[i]] * f[low[i] * prime[j]];
break;
}
low[i * prime[j]] = prime[j];
f[i * prime[j]] = f[i] * f[prime[j]];
}
}
for (int i = 2; i < MAXN; i++) f[i] = 2 * f[i] - i, f[i] += f[i - 1];
}
LL calc(LL n, LL m) {
LL ans = 0, l, r;
for (l = 1; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (phi[r] - phi[l - 1]) * (n / l) * (m / l);
}
return ans;
}
int type, t;
int main() {
seive1();
n = read(), m = read();
printf("%lld\n", 2 * calc(n, m) - n * m);
return 0;
}
「SDOI2017」数字表格
好难啊。
\(T\) 次询问,每次询问给出 \(n,m\) 。求
把指数给提出来,指数太恶心了。
老套路,设 \(T=dD\) ,代回原式。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int MAXN = 1e6 + 5;
bool vis[MAXN];
int len, mu[MAXN], prime[MAXN];
LL f1[MAXN], f2[MAXN], s[MAXN];
const LL mod = 1e9 + 7;
LL qkpow(LL x, LL power, LL mod) {
x %= mod;
LL ans = 1;
for (; power; power >>= 1, (x *= x) %= mod)
if (power & 1)
(ans *= x) %= mod;
return ans;
}
void Getmu() {
mu[1] = 1;
f1[1] = 1, f2[1] = 1;
s[0] = s[1] = 1;
for (int i = 2; i < MAXN; i++) {
f1[i] = (f1[i - 1] + f1[i - 2]) % mod;
f2[i] = qkpow(f1[i], mod - 2, mod);
s[i] = 1;
if (!vis[i])
prime[++len] = i, mu[i] = -1;
for (int j = 1; j <= len && i * prime[j] < MAXN; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for (int i = 1; i <= MAXN - 5; i++) {
if (!mu[i])
continue;
for (int j = i; j <= MAXN - 5; j += i) s[j] = s[j] * (mu[i] == 1 ? f1[j / i] : f2[j / i]) % mod;
}
for (int i = 1; i < MAXN; i++) s[i] = s[i] * s[i - 1] % mod;
}
int T;
LL n, m;
LL calc(LL n, LL m) {
LL ans = 1;
for (LL i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
ans = ans * qkpow(s[j] % mod * qkpow(s[i - 1], mod - 2, mod), (n / i) * (m / i), mod) % mod;
}
return ans;
}
int main() {
Getmu();
scanf("%d", &T);
while (T--) {
scanf("%lld %lld", &n, &m);
printf("%lld\n", calc(n, m));
}
return 0;
}
括号里面的直接整除分块就好了。