寫在前面
本文章已同步發佈在博主的博客園,也可以去那裏看
這是蒟蒻第一次寫這麼長的博文
如果覺得寫得湊合就點個支持吧
前置知識
積性函數、狄利克雷卷積、數論分塊(這一篇去找gyh吧我講也講不好)(有空慢慢補)
Mobius函數
定義
莫比烏斯函數定義爲:
其中是不同素數。
可以看出,當沒有平方因子時,非零。
也是積性函數。
性質
莫比烏斯函數具有如下的性質:
使用狄利克雷卷積來表示,即
證明:
時顯然成立。
若,設有個不同的素因子,由於當且僅當無平方因子,故中每個素因子的指數只能爲或。
若的某個因子,有,則它由個本質不同的質因子構成。因爲質因子的總數爲,所以滿足上式的因子數有個。
所以我們就可以對於原式,轉化爲枚舉的值,同時運用二項式定理,故有
該式在即時爲1,否則爲。
求莫比烏斯函數
因爲莫比烏斯函數是積性函數,所以可以用線性篩
int n, cnt, p[A], mu[A];
bool vis[A];
void getmu() {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) mu[i] = -1, p[++cnt] = i;
for (int j = 1; j <= cnt && i * p[j] <= n; j++) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) { mu[i * p[j]] = 0; break; }
mu[i * p[j]] -= mu[i];
}
}
}
莫比烏斯反演公式
設,爲兩個數論函數。
如果有
則有
證明
-
法一:對原式做數論變換
-
替換,即
-
變換求和順序得
-
因爲,所以只有在即時纔會成立,所以上式等價於
得證
-
-
法二:利用卷積
原問題爲:已知,證明
轉化:()
再次得證= =
小性質
證明
-
法一:
設,那麼等式右邊等式左邊
-
法二:
利用單位函數暴力拆開:
做題思路&&應用
利用狄利克雷卷積可以解決一系列求和問題。常見做法是使用一個狄利克雷卷積替換求和式中的一部分,然後調換求和順序,最終降低時間複雜度。
經常利用的卷積有和。
題
還是以題爲主吧,但是做的題也會單獨寫題解,畢竟要多水幾篇博客的嘛/huaji
洛谷 P2522 [HAOI2011]Problem b
有組詢問,每次給出,求
設
那麼根據容斥原理,題目中的式子就轉化成了
所以我們接下來的問題就轉化爲了如何求的值
現在來化簡的值
-
容易得出原式等價於
-
因爲,由此可將原式化爲
-
改變枚舉對象並改變枚舉順序,先枚舉,得
也就是說當且時,
-
易得中一共有個的倍數,同理中一共有個的倍數,於是原式化爲
此時已經可以求解,但是過不了,因爲有很多值相同的區間,所以可以用數論分塊來做
先預處理,再用數論分塊,複雜度
我的代碼每次得分玄學,看評測機心情,建議自己寫
/*
Author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int A = 1e6 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
int n, a, b, c, d, k, cnt, p[A], mu[A], sum[A];
bool vis[A];
void getmu() {
int MAX = 50010;
mu[1] = 1;
for (int i = 2; i <= MAX; i++) {
if (!vis[i]) mu[i] = -1, p[++cnt] = i;
for (int j = 1; j <= cnt && i * p[j] <= MAX; j++) {
vis[i * p[j]] = true;
if (i % p[j] == 0) break;
mu[i * p[j]] -= mu[i];
}
}
for (int i = 1; i <= MAX; i++) sum[i] = sum[i - 1] + mu[i];
}
int work(int x, int y) {
int ans = 0ll;
int max = min(x, y);
for (int l = 1, r; l <= max; l = r + 1) {
r = min(x / (x / l), y / (y / l));
ans += (1ll * x / (l * k)) * (1ll * y / (l * k)) * 1ll * (sum[r] - sum[l - 1]);
}
return ans;
}
void solve() {
a = read(), b = read(), c = read(), d = read(), k = read();
cout << work(b, d) - work(a - 1, d) - work(b, c - 1) + work(a - 1, c - 1) << '\n';
}
signed main() {
getmu();
int T = read();
while (T--) solve();
return 0;
}
洛谷 P3455 [POI2007]ZAP-Queries
有組詢問,每次詢問求
因爲我不喜歡用,所以一一對應換成
直接淦式子(太長了CSDN加載不出來於是換成了截圖)
現在就可以每次詢問做這道題了
但是跑不過啊,不過顯然可以數論分塊,所以我們就可以回答每次詢問了
/*
Author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
int n, m, k, mu[A], p[A], sum[A], cnt;
bool vis[A];
void getmu(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) p[++cnt] = i, mu[i] = -1;
for (int j = 1; j <= cnt && i * p[j] <= n; j++) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) break;
mu[i * p[j]] -= mu[i];
}
}
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}
int solve(int n, int m, int k) {
int ans = 0, maxn = min(n, m);
for (int l = 1, r; l <= maxn; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (sum[r] - sum[l - 1]) * (n / (k * l)) * (m / (k * l));
}
return ans;
}
int main() {
getmu(50000);
int T = read();
while (T--) {
n = read(), m = read(), k = read();
cout << solve(n, m, k) << '\n';
}
return 0;
}
洛谷 P1829 [國家集訓隊]Crash的數字表格 / JZPTAB
求
容易想到原式等價於
枚舉的最大公約數,顯然,即和互質
變換求和順序
記
對其進行化簡,用替換
轉化爲首先枚舉約數
設,則可以進一步轉化
前半段可以處理前綴和,後半段可以求,設
顯然可以求解
到現在
可以用數論分塊求解
迴帶到原式中
又可以數論分塊求解了
然後就做完啦
/*
Author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int A = 1e7 + 11;
const int B = 1e6 + 11;
const int mod = 20101009;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
bool vis[A];
int n, m, mu[A], p[B], sum[A], cnt;
void getmu() {
mu[1] = 1;
int k = min(n, m);
for (int i = 2; i <= k; i++) {
if (!vis[i]) p[++cnt] = i, mu[i] = -1;
for (int j = 1; j <= cnt && i * p[j] <= k; ++j) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) break;
mu[i * p[j]] = -mu[i];
}
}
for (int i = 1; i <= k; i++) sum[i] = (sum[i - 1] + i * i % mod * mu[i]) % mod;
}
int Sum(int x, int y) {
return (x * (x + 1) / 2 % mod) * (y * (y + 1) / 2 % mod) % mod;
}
int solve2(int x, int y) {
int res = 0;
for (int i = 1, j; i <= min(x, y); i = j + 1) {
j = min(x / (x / i), y / (y / i));
res = (res + 1LL * (sum[j] - sum[i - 1] + mod) * Sum(x / i, y / i) % mod) % mod;
}
return res;
}
int solve(int x, int y) {
int res = 0;
for (int i = 1, j; i <= min(x, y); i = j + 1) {
j = min(x / (x / i), y / (y / i));
res = (res + 1LL * (j - i + 1) * (i + j) / 2 % mod * solve2(x / i, y / i) % mod) % mod;
}
return res;
}
signed main() {
n = read(), m = read();
getmu();
cout << solve(n, m) << '\n';
}
洛谷 P2257 YY的GCD
給定,求二元組的個數,滿足,且是素數。
,自帶多組數據,至多組數據。
思路與第一題Problem B類似,在這裏不再贅述,只給出代碼= =
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int A = 1e7 + 11;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for ( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
bool vis[A];
long long sum[A];
int prim[A], mu[A], g[A], cnt, n, m;
void get_mu(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
mu[i] = -1;
prim[++cnt] = i;
}
for (int j = 1; j <= cnt && prim[j] * i <= n; j++) {
vis[i * prim[j]] = 1;
if (i % prim[j] == 0) break;
else mu[prim[j] * i] = - mu[i];
}
}
for (int j = 1; j <= cnt; j++)
for (int i = 1; i * prim[j] <= n; i++) g[i * prim[j]] += mu[i];
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (long long)g[i];
}
signed main() {
int t = read();
get_mu(10000000);
while (t--) {
n = read(), m = read();
if (n > m) swap(n, m);
long long ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += 1ll * (n / l) * (m / l) * (sum[r] - sum[l - 1]);
}
cout << ans << '\n';
}
return 0;
}
洛谷 P3327 [SDOI2015]約數個數和
求
爲的約數個數和
需要用到
證明我也不會
然後自己推導吧,在此不再贅述
/*
Author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
bool vis[A];
int n, m, p[A], mu[A], cnt, sum[A];
long long g[A], ans;
void getmu(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) mu[i] = -1, p[++cnt] = i;
for (int j = 1; j <= cnt && i * p[j] <= n; j++) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) break;
mu[i * p[j]] -= mu[i];
}
}
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
for (int i = 1; i <= n; i++) {
int ans = 0;
for (int l = 1, r; l <= i; l = r + 1) {
r = (i / (i / l));
ans += 1ll * (r - l + 1) * (i / l);
}
g[i] = ans;
}
}
signed main() {
int T = read();
getmu(50000);
while (T--) {
n = read(), m = read();
int maxn = min(n, m);
ans = 0;
for (int l = 1, r; l <= maxn; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += 1ll * (sum[r] - sum[l - 1]) * 1ll * g[n / l] * 1ll * g[m / l];
}
cout << ans << '\n';
}
return 0;
}
洛谷 P4449 於神之怒加強版
求
還是直接淦式子
令,則原式等於
顯然前面的部分可以分塊求解。
現在考慮後面的一部分,令
容易得出這個函數是積性函數,所以我們就可以線性篩然後求出其前綴和
然後就做完了
/*
Author:loceaner
莫比烏斯反演
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int A = 5e6 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar();
int x = 0, f = 1;
for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
bool vis[A];
int T, n, m, k, f[A], g[A], p[A], cnt, sum[A];
inline int power(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod, b >>= 1;
}
return res;
}
inline int mo(int x) {
if(x > mod) x -= mod;
return x;
}
inline void work() {
g[1] = 1;
int maxn = 5e6 + 1;
for (int i = 2; i <= maxn; i++) {
if (!vis[i]) { p[++cnt] = i, f[cnt] = power(i, k), g[i] = mo(f[cnt] - 1 + mod); }
for (int j = 1; j <= cnt && i * p[j] <= maxn; j++) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) { g[i * p[j]] = g[i] * 1ll * f[j] % mod; break; }
g[i * p[j]] = g[i] * 1ll * g[p[j]] % mod;
}
}
for (int i = 2; i <= maxn; i++) g[i] = (g[i - 1] + g[i]) % mod;
}
inline int abss(int x) {
while (x < 0) x += mod;
return x;
}
signed main() {
T = read(), k = read();
work();
while (T--) {
n = read(), m = read();
int maxn = min(n, m), ans = 0;
for (int l = 1, r; l <= maxn; l = r + 1) {
r = min(n / (n / l), m / (m / l));
(ans += abss(g[r] - g[l - 1]) * 1ll * (n / l) % mod * (m / l) % mod) %= mod;
}
ans = (ans % mod + mod) % mod;
cout << ans << '\n';
}
return 0;
}