『题解』Luogu-P1587 [NOI2016] 循环之美

P1587 [NOI2016] 循环之美

Description

  • 给定十进制数 \(n, m, k\),求在 \(k\) 进制下有多少个 值不相等纯循环 小数可以用分数 \(\dfrac{x}{y}\) 表示,其中 \(1\le x\le n, 1\le y\le m, x, y \in \mathbb{N}^*\)。
  • \(1\le n, m\le 10^9, 2\le k\le 2\times 10^3\)。

Solution

Step 1:转化问题

首先这些数的值要互不相等,那不妨只计算满足要求的 最简分数 的数量,即 \(\gcd(x, y) = 1\)。

根据你丰富的小奥知识得到:如果在 \(k\) 进制下分数 \(\dfrac{x}{y}\) 为最简分数,则 \(\gcd(y, k) = 1\) 是其可化成纯循环小数的 充要条件

证明:

设 \(\dfrac{x}{y}\) 化成的纯循环小数的循环节长度为 \(l\),则有 \(\left\{\dfrac{x}{y} \right\} = \left\{\dfrac{x}{y} \cdot k^l \right\}\)。

乘 \(k^l\) 其实就是将小数点向后移动 \(l\) 位,小数部分还是不变的。

根据 \(\{x\} = x - \lfloor x\rfloor\),有

\[\dfrac{x}{y} - \left\lfloor\dfrac{x}{y}\right\rfloor = \dfrac{x k^l}{y} - \left\lfloor\dfrac{x k^l}{y}\right\rfloor \\ x - \left\lfloor\dfrac{x}{y}\right\rfloor y = x k^l - \left\lfloor\dfrac{x k^l}{y} \right\rfloor y \\ x \equiv x k^l \pmod y \\ \because \gcd(x, y) = 1 \\ \therefore k^l \equiv 1 \pmod y \\ \therefore \gcd(y, k) = 1 \]

所以我们要求的其实就是

\[\sum_{x = 1}^n \sum_{y = 1}^m [\gcd(x, y) = 1] [\gcd(y, k) = 1] \]

Step 2:莫比乌斯反演

先将 \(x \Leftrightarrow i, y \Leftrightarrow j\),不妨设 \(n\le m\)。

\[\begin{aligned} ans & = \sum_{i = 1}^n \sum_{j = 1}^m [\gcd(j, k) = 1] \sum_{d\mid \gcd(i, j)} \mu(d) \\ & = \sum_{d = 1}^n \mu(d) \left\lfloor\dfrac{n}{d}\right\rfloor \sum_{j = 1}^{\left\lfloor\frac{m}{d}\right\rfloor} [\gcd(jd, k) = 1] \\ & = \sum_{d = 1}^n [\gcd(d, k) = 1] \mu(d) \left\lfloor\dfrac{n}{d}\right\rfloor \sum_{j = 1}^{\left\lfloor\frac{m}{d}\right\rfloor} [\gcd(j, k) = 1] \end{aligned} \]

已经化到最简了,考虑设函数。

我们对

\[f(n) = \sum_{i = 1}^n [\gcd(i, k) = 1] \]

很熟悉,因为它是每 \(k\) 个一循环,即

\[f(n) = \left\lfloor\dfrac{n}{k}\right\rfloor \varphi(k) + f(n\bmod k) \]

又因为 \(0\le n\bmod k < k\),所以可以 \(\Omicron(k\log k)\) 暴力预处理 \(f(0) \sim f(k)\),\(f(k)\) 直接当 \(\varphi(k)\) 用。

将 \(f\) 代回原式

\[ans = \sum_{d = 1}^n [\gcd(d, k) = 1] \mu(d) \left\lfloor\dfrac{n}{d}\right\rfloor f\left(\left\lfloor\dfrac{m}{d}\right\rfloor \right) \]

后面两项打包整除分块,

因为 \(n\le 10^9\),明显需要杜教筛。

前面两项的前缀和

\[g(n) = \sum_{i = 1}^n [\gcd(i, k) = 1] \mu(i) \]

用类似杜教筛的思路

\[\begin{aligned} g(n) & = [\gcd(1, k) = 1] g\left(\left\lfloor\dfrac{n}{1}\right\rfloor \right) \\ & = \sum_{i = 1}^n [\gcd(i, k) = 1] g\left(\left\lfloor\dfrac{n}{i}\right\rfloor \right) - \sum_{i = 2}^n [\gcd(i, k) = 1] g\left(\left\lfloor\dfrac{n}{i}\right\rfloor \right) \end{aligned} \]

而后面一项中的 \([\gcd(i, k) = 1]\) 的前缀和其实就是已经处理过的 \(f\)!

所以只要处理出前面那一项就可以完全仿照杜教筛递归 + 记忆化计算了。

\[\begin{aligned} \sum_{i = 1}^n [\gcd(i, k) = 1] g\left(\left\lfloor\dfrac{n}{i}\right\rfloor \right) & = \sum_{i = 1}^n [\gcd(i, k) = 1] \sum_{j = 1}^{\left\lfloor\frac{n}{i}\right\rfloor} [\gcd(j, k) = 1] \mu(j) \\ & = \sum_{i = 1}^n \sum_{j = 1}^{\left\lfloor\frac{n}{i}\right\rfloor} [\gcd(ij, k) = 1] \mu(j) \\ & = \sum_{T = 1}^n [\gcd(T, k) = 1] \sum_{d\mid T} \mu(d) \\ & = \sum_{T = 1}^n [\gcd(T, k) = 1] (\mu * \mathbf{1})(T) \\ & = \sum_{T = 1}^n [\gcd(T, k) = 1] \varepsilon(T) \\ & = 1 \end{aligned} \]

所以

\[g(n) = 1 - \sum_{i = 2}^n [\gcd(i, k) = 1] g\left(\left\lfloor\dfrac{n}{i}\right\rfloor \right) \]

杜教筛即可做到 \(\Omicron(n^{\frac{2}{3}})\) 计算。

总时间复杂度为 \(\Omicron(k\log k + n^{\frac{2}{3}})\)。

Tips : You should use long long .

Warning : You cannot swap(n, m) !!!

Code

//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#include <unordered_map>
#define Debug(x) cout << #x << "=" << x << endl
#define int long long
using namespace std;

const int MAXN = 1e6 + 5;
const int N = 1e6;
const int MAXK = 2e3 + 5;
const int K = 2e3;

int k;
int p[MAXN], mu[MAXN], g[MAXN], f[MAXK];
bool vis[MAXN];

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

int gcd(int a, int b)
{
	if (!b)
	{
		return a;
	}
	return gcd(b, a % b);
}

void pre()
{
	for (int i = 1; i <= k; i++)
	{
		f[i] = f[i - 1] + (gcd(i, k) == 1);
	}
	mu[1] = g[1] = 1;
	for (int i = 2; i <= N; i++)
	{
		if (!vis[i])
		{
			p[++p[0]] = i;
			mu[i] = -1;
		}
		g[i] = g[i - 1] + (GetF(i) - GetF(i - 1)) * mu[i];
		for (int j = 1; j <= p[0] && i * p[j] <= N; j++)
		{
			vis[i * p[j]] = true;
			if (i % p[j] == 0)
			{
				mu[i * p[j]] = 0;
				break;
			}
			mu[i * p[j]] = mu[i] * mu[p[j]];
		}
	}
}

int GetSumF(int l, int r)
{
	return GetF(r) - GetF(l - 1);
}

unordered_map<int, int> dp;

int sublinear(int n)
{
	if (n <= N)
	{
		return g[n];
	}
	if (dp.count(n))
	{
		return dp[n];
	}
	int res = 1;
	for (int l = 2, r; l <= n; l = r + 1)
	{
		int k = n / l;
		r = n / k;
		res -= GetSumF(l, r) * sublinear(k);
	}
	return dp[n] = res;
}

int GetSumG(int l, int r)
{
	return sublinear(r) - sublinear(l - 1);
}

int block(int n, int m)
{
	int res = 0;
	for (int l = 1, r; l <= n; l = r + 1)
	{
		int k1 = n / l, k2 = m / l;
		if (!k2)
		{
			break;
		}
		r = min(n / k1, m / k2);
		res += GetSumG(l, r) * k1 * GetF(k2);
	}
	return res;
}

signed main()
{
	int n, m;
	scanf("%lld%lld%lld", &n, &m, &k);
	pre();
	printf("%lld\n", block(n, m));
	return 0;
}
上一篇:杜教筛(进阶篇)


下一篇:实验二 数字类型及其操作(新)