[NOI2016] 循环之美

填坑。

先考虑题面,若 \(i \over j\) 最简,必定有 \(i \perp j\)。 然后要在 \(k\) 进制下循环,设循环节长为 \(x\),有小学奥数经典套路:

\[{i\over j} - \lfloor{i \over j}\rfloor = {ik^x \over j} - \lfloor{ik^x\over j}\rfloor \]

两边都乘 \(j\), 再对 \(j\) 取模,约分掉 \(i\) 得到:

\[k^x \equiv 1 \pmod j \]

由欧拉定理知,满足 \(j\perp k\) 一定有解。而不满足的话,\(\gcd(k,j)\neq 1\), \(\gcd(k^x,j)\neq 1\),而 \(k^x\bmod j = 1\), \(\gcd(k^x,j)=\gcd(k^x,k^x\bmod j)=\gcd(k^x,1)=1\),前后矛盾,无解。

因此我们可以得到式子:

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

注意 \(i\perp j\) 的含义是 \(\gcd(i,j)=1\),我们得到:

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

所以有:

\[\sum_{d}\mu(d)\sum_{i=1}^{n\over d} \sum_{j = 1}^{m\over d}[jd⊥ k] \]

\([jd⊥k]\) 可以拆成 \([j⊥k]\)\([d⊥k]\)。后面的式子和 \(i\) 没什么关系,直接乘上 \(n\over d\):

\[\sum_{d}\mu(d){n\over d}[d⊥k]\sum_{j=1}^{m\over d} [j⊥k] \]

我们令 \(g(n)\) 表示后面那个和式,即:

\[g(n)=\sum_{i=1}^n [j⊥k] \]

由于 \(\gcd(i,k)=\gcd(i + k, k)\),所以:

\[\sum_{i=ak+1}^{(a+1)k}[i⊥k]=\sum_{i=1}^k [i⊥k]=\varphi(k) \]

我们轻松得到:\(g(n)=g(n \bmod k) + {n\over k}\varphi(k)\)\(\Theta(k\log k)\) 预处理出 \(g(1)~g(k)\) 后可以 \(\Theta(1)\) 求。

带回原式得到:

\[\sum_{d}{n\over d}g({m\over d})\mu(d)[d⊥k] \]

前面两项可以整除分块搞,考虑后面的怎么求前缀和,即求。

\[f(n)=\sum_{i=1}^n \mu(i)[i⊥k] \]

直接推有点难。考虑杜教筛。难点在于求和 \([i ⊥ k]\) 卷起来后的前缀和,简单推一下:

\[\sum_{i=1}^n \sum_{d|i} \mu(d)[d⊥k][{i\over d} ⊥ k]=\sum_{i=1}^n [i⊥k][i=1]=1 \]

于是我们就愉快的 AC 了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>

using namespace std;

const int N = 2e3 + 5, Lim = 1e6 + 5, lim = 1e6;

int mu[Lim], g[N];

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

int prime[Lim], top = 0, notprime[Lim], phk = 0, n, m, k, f[Lim];

map<int, int> mp;

inline void pre() {
	mu[1] = 1; notprime[1] = 1;
	for (int i = 2; i <= lim; ++i) {
		if (!notprime[i]) prime[++top] = i, mu[i] = -1;
		for (int j = 1; j <= top && i * prime[j] <= Lim; ++j) {
			notprime[i * prime[j]] = 1;
			if (i % prime[j]) mu[i * prime[j]] = -mu[i];
			else { mu[i * prime[j]] = 0; break; }
		}
	} for (int i = 1; i <= k; ++i) g[i] = g[i - 1] + (gcd(i, k) == 1);
	phk = g[k];
	for (int i = 1; i <= lim; ++i) f[i] = f[i - 1] + (gcd(i, k) == 1) * mu[i];
}

typedef long long ll;

inline int G(int x) {
	return (x / k) * phk + g[x % k]; 
}

inline int min_(int a, int b) {
	return a < b ? a : b;
}

inline int F(int n) {
	if (n <= lim) return f[n];
	if (mp[n]) return mp[n];
	int ret = 1;
	for (int l = 2, r; l <= n; l = r + 1) {
		r = n / (n / l);
		ret -= F(n / l) * (G(r) - G(l - 1));
	} return mp[n] = ret;
}

int main() {
	scanf("%d%d%d", &n, &m, &k);
	pre(); ll ans = 0;
	for (int l = 1, r; l <= min_(n, m); l = r + 1) {
		r = min_(n / (n / l), m / (m / l));
		ans += 1ll * (n / l) * G(m / l)  * (F(r) - F(l - 1));
	} printf("%lld\n", ans);
	return 0;
}
posted @ 2021-08-01 16:29  Smallbasic  阅读(74)  评论(0)    收藏  举报