「算法筆記」莫比烏斯反演進階

YY 的 GCD

題目大意

求下列式子的值:

i=1nj=1m[gcd{i,j}P] (P 爲素數集合)\sum_{i = 1}^{n} \sum_{j = 1}^{m} [\gcd\{i, j\} \in P]\ (P\ \text{爲素數集合})

10410^4 組詢問,n,m107n, m \le 10^7

思路分析

將原式變形:


pPi=1npj=1mp[gcd{i,j}=1]\sum_{p \in P} \sum_{i = 1}^{\lfloor \frac{n}{p} \rfloor} \sum_{j = 1}^{\lfloor \frac{m}{p} \rfloor} [\gcd\{i, j\} = 1]

pPi=1npj=1mpdi,djμ(d)\sum_{p \in P} \sum_{i = 1}^{\lfloor \frac{n}{p} \rfloor} \sum_{j = 1}^{\lfloor \frac{m}{p} \rfloor} \sum_{d | i, d | j} \mu(d)

pPd=1min{np,np}μ(d)i=1npdj=1mpd1\sum_{p \in P} \sum_{d = 1}^{\min\{\lfloor \frac{n}{p} \rfloor, \lfloor \frac{n}{p} \rfloor\}} \mu(d) \sum_{i = 1}^{\lfloor \frac{n}{pd} \rfloor} \sum_{j = 1}^{\lfloor \frac{m}{pd} \rfloor} 1

pPd=1min{np,np}μ(d)npdmpd\sum_{p \in P} \sum_{d = 1}^{\min\{\lfloor \frac{n}{p} \rfloor, \lfloor \frac{n}{p} \rfloor\}} \mu(d) \lfloor \frac{n}{pd} \rfloor \lfloor \frac{m}{pd} \rfloor

T=1min{n,m}pT,pPμ(Tp)nTmT\sum_{T = 1}^{\min\{n, m\}} \sum_{p | T, p \in P} \mu(\frac{T}{p}) \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor

T=1min{n,m}nTmTpT,pPμ(Tp)\sum_{T = 1}^{\min\{n, m\}} \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \color{red}{\sum_{p | T, p \in P} \mu(\frac{T} {p})}


紅色部分可以使用線性篩算出 μ\mu,然後枚舉每個素數並更新它的倍數算出,時間複雜度 O(nloglogn)O(n \log \log n)。黑色部分可以使用數論分塊的技巧求出。

代碼實現

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long llong;
const int maxn = 1e7;
int T, n, m, p[maxn + 3];
int mrk[maxn + 3], miu[maxn + 3], sum[maxn + 3];

int main() {
	miu[1] = 1;
	for (int i = 2; i <= maxn; i++) {
		if (!mrk[i]) p[++m] = i, miu[i] = -1;
		for (int j = 1, t; j <= m && (t = i * p[j]) <= maxn; j++) {
			mrk[t] = 1;
			if (i % p[j] == 0) break;
			miu[t] = -miu[i];
		}
	}
	for (int i = 1; i <= m; i++) {
		for (int j = 1, t; (t = p[i] * j) <= maxn; j++) {
			sum[t] += miu[j];
		}
	}
	for (int i = 1; i <= maxn; i++) {
		sum[i] += sum[i - 1];
	}
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d", &n, &m);
		if (n > m) swap(n, m);
		llong ans = 0;
		for (int i = 1, j; i <= n; i = j + 1) {
			j = min(n / (n / i), m / (m / i));
			ans += 1ll * (n / i) * (m / i) * (sum[j] - sum[i - 1]);
		}
		printf("%lld\n", ans);
	}
	return 0;
}

NOI 2016 循環之美

題目大意

求下列式子的值:

i=1nj=1m[gcd{i,j}=1][gcd{j,k}=1]\sum_{i = 1}^{n} \sum_{j = 1}^{m} [\gcd\{i, j\} = 1] [\gcd\{j, k\} = 1]

n,m109,k2000n, m \le 10^9, k \le 2000

思路分析

將原式變形:


i=1m[gcd{i,k}=1]j=1n[gcd{i,j}=1]\sum_{i = 1}^{m} [\gcd\{i, k\} = 1] \sum_{j = 1}^{n} [\gcd\{i, j\} = 1]

i=1m[gcd{i,k}=1]j=1ndi,jμ(d)\sum_{i = 1}^{m} [\gcd\{i, k\} = 1] \sum_{j = 1}^{n} \sum_{d | i, j} \mu(d)

d=1min{n,m}μ(d)i=1md[gcd{id,k}=1]j=1nd1\sum_{d = 1}^{\min\{n, m\}} \mu(d) \sum_{i = 1}^{\lfloor \frac{m}{d} \rfloor}[\gcd\{i \cdot d, k\} = 1] \sum_{j = 1}^{\lfloor \frac{n}{d} \rfloor} 1

d=1min{n,m}μ(d)[gcd{d,k}=1]ndi=1md[gcd{i,k}=1]\sum_{d = 1}^{\min\{n, m\}} \color{blue}{\mu(d) \cdot [\gcd\{d, k\} = 1]} \cdot \lfloor \frac{n}{d} \rfloor\sum_{i = 1}^{\lfloor \frac{m}{d} \rfloor}\color{red}{[\gcd\{i, k\} = 1]}


現在瓶頸就變成了計算紅色部分和藍色部分的前綴和。

注意到 k2000k \le 2000,於是紅色部分直接可以遞推計算 k\le k 的部分,然後在乘上計數的次數即可。

對於藍色部分,令 S(n,k)=i=1n[gcd(i,k)=1]μ(i)S(n, k) = \sum_{i = 1}^{n} [\gcd(i, k) = 1] \cdot \mu(i)。對原式進行變形,得到原式 =dkμ(d)i=1ndμ(id)= \sum_{d | k} \mu(d) \sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor} \mu(i \cdot d)。發現如果 gcd(i,d)1\gcd(i, d) \neq 1,那麼 μ(id)=0\mu(i \cdot d) = 0,對答案沒有影響。於是可以繼續化簡,得到 S(n,k)=dkμ(d)2S(nd,d)S(n, k) = \sum_{d | k} \mu(d) ^ 2 S(\lfloor \frac{n}{d} \rfloor, d)。直接記憶化搜索 ++ 杜教篩即可。

代碼實現

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;

typedef pair<int, int> pii;
const int maxm = 1e7, maxk = 2000;
bool mrk[maxm + 3];
int n, m, k, cnt, p[maxm + 3], miu[maxm + 3], sum[maxm + 3], f[maxk + 3];
map<pii, int> mp;

int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}

int F(int n) {
	return f[n % k] + n / k * f[k];
}

int S(int n, int k) {
	if ((k == 1 && n <= maxm) || !n) {
		return sum[n];
	}
	pii x = pii(n, k);
	if (mp.find(x) != mp.end()) {
		return mp[x];
	}
	int res;
	if (k == 1) {
		res = 1;
		for (int l = 2, r; l <= n; l = r + 1) {
			r = n / (n / l);
			res -= (r - l + 1) * S(n / l, 1);
		}
	} else {
		res = 0;
		for (int i = 1, j; i * i <= k; i++) {
			if (k % i == 0) {
				j = k / i;
				if (miu[i]) res += S(n / i, i);
				if (miu[j] && i * i != k) res += S(n / j, j);
			}
		}
	}
	return mp[x] = res;
}

int main() {
	#ifdef LOCAL
		freopen("input.md", "r", stdin);
		freopen("output.md", "w", stdout);
	#endif
	miu[1] = 1;
	for (int i = 2; i <= maxm; i++) {
		if (!mrk[i]) {
			p[++cnt] = i;
			miu[i] = -1;
		}
		for (int j = 1, t; j <= cnt && (t = i * p[j]) <= maxm; j++) {
			mrk[t] = 1;
			if (i % p[j] == 0) break;
			miu[t] = -miu[i];
		}
	}
	for (int i = 1; i <= maxm; i++) {
		sum[i] = sum[i - 1] + miu[i];
	}
	scanf("%d %d %d", &n, &m, &k);
	for (int i = 1; i <= k; i++) {
		f[i] = f[i - 1] + (gcd(i, k) == 1);
	}
	int x = min(n, m);
	long long ans = 0;
	for (int l = 1, r; l <= x; l = r + 1) {
		r = min(n / (n / l), m / (m / l));
		ans += 1ll * (n / l) * F(m / l) * (S(r, k) - S(l - 1, k));
	}
	printf("%lld\n", ans);
	return 0;
}

UOJ 62 怎樣跑的更快

題目大意

神仙題,參考官方題解

思路分析

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

代碼實現

#include <cstdio>

const int maxn = 1e5, mod = 998244353;
int n, c, d, q, f_r[maxn + 3], f_z[maxn + 3], hx[maxn + 3];

int qpow(int a, int b) {
	int c = 1;
	for (; b; b >>= 1, a = 1ll * a * a % mod) {
		if (b & 1) c = 1ll * a * c % mod;
	}
	return c;
}

void add(int &a, int b) {
	a += b;
	a < mod ? NULL : a -= mod;
}

void sub(int &a, int b) {
	a -= b;
	a < 0 ? a += mod : NULL;
}

void solve(int n, int f[], int type) {
	if (!type) {
		for (int i = 1; i <= n; i++) {
			for (int j = 2 * i; j <= n; j += i) {
				sub(f[j], f[i]);
			}
		}
	} else {
		for (int i = n; i; i--) {
			for (int j = 2 * i; j <= n; j += i) {
				sub(f[i], f[j]);
			}
		}
	}
}

int main() {
	#ifdef LOCAL
		freopen("input.md", "r", stdin);
		freopen("output.md", "w", stdout);
	#endif
	scanf("%d %d %d %d", &n, &c, &d, &q);
	c %= mod - 1, d %= mod - 1;
	c = (c - d + mod - 1) % (mod - 1);
	for (int i = 1; i <= n; i++) {
		f_r[i] = qpow(i, c);
	}
	solve(n, f_r, 0);
	while (q--) {
		for (int i = 1; i <= n; i++) {
			scanf("%d", &f_z[i]);
			f_z[i] = 1ll * f_z[i] * qpow(i, mod - 1 - d) % mod;
		}
		solve(n, f_z, 0);
		bool flag = true;
		for (int i = 1; i <= n; i++) {
			if (f_r[i] > 0) {
				hx[i] = 1ll * f_z[i] * qpow(f_r[i], mod - 2) % mod;
			} else if (f_z[i] > 0) {
				flag = false;
				break;
			}
		}
		if (!flag) {
			puts("-1");
			continue;
		}
		solve(n, hx, 1);
		for (int i = 1; i <= n; i++) {
			printf("%d%c", 1ll * hx[i] * qpow(i, mod - 1 - d) % mod, " \n"[i == n]);
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章