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

P1587 [NOI2016] 循环之美

Description

  • 给定十进制数 n , m , k n, m, k n,m,k,求在 k k k 进制下有多少个 值不相等纯循环 小数可以用分数 x y \dfrac{x}{y} yx 表示,其中 1 ≤ x ≤ n , 1 ≤ y ≤ m , x , y ∈ N ∗ 1\le x\le n, 1\le y\le m, x, y \in \mathbb{N}^* 1xn,1ym,x,yN
  • 1 ≤ n , m ≤ 1 0 9 , 2 ≤ k ≤ 2 × 1 0 3 1\le n, m\le 10^9, 2\le k\le 2\times 10^3 1n,m109,2k2×103

Solution

Step 1:转化问题

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

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

证明:

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

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

根据 { x } = x − ⌊ x ⌋ \{x\} = x - \lfloor x\rfloor {x}=xx,有
x y − ⌊ x y ⌋ = x k l y − ⌊ x k l y ⌋ x − ⌊ x y ⌋ y = x k l − ⌊ x k l y ⌋ y x ≡ x k l ( m o d y ) ∵ gcd ⁡ ( x , y ) = 1 ∴ k l ≡ 1 ( m o d y ) ∴ gcd ⁡ ( y , k ) = 1 \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 yxyx=yxklyxklxyxy=xklyxklyxxkl(mody)gcd(x,y)=1kl1(mody)gcd(y,k)=1

所以我们要求的其实就是
∑ x = 1 n ∑ y = 1 m [ gcd ⁡ ( x , y ) = 1 ] [ gcd ⁡ ( y , k ) = 1 ] \sum_{x = 1}^n \sum_{y = 1}^m [\gcd(x, y) = 1] [\gcd(y, k) = 1] x=1ny=1m[gcd(x,y)=1][gcd(y,k)=1]

Step 2:莫比乌斯反演

先将 x ⇔ i , y ⇔ j x \Leftrightarrow i, y \Leftrightarrow j xi,yj,不妨设 n ≤ m n\le m nm

a n s = ∑ i = 1 n ∑ j = 1 m [ gcd ⁡ ( j , k ) = 1 ] ∑ d ∣ gcd ⁡ ( i , j ) μ ( d ) = ∑ d = 1 n μ ( d ) ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd ⁡ ( j d , k ) = 1 ] = ∑ d = 1 n [ gcd ⁡ ( d , k ) = 1 ] μ ( d ) ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd ⁡ ( j , k ) = 1 ] \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} ans=i=1nj=1m[gcd(j,k)=1]dgcd(i,j)μ(d)=d=1nμ(d)dnj=1dm[gcd(jd,k)=1]=d=1n[gcd(d,k)=1]μ(d)dnj=1dm[gcd(j,k)=1]
已经化到最简了,考虑设函数。

我们对
f ( n ) = ∑ i = 1 n [ gcd ⁡ ( i , k ) = 1 ] f(n) = \sum_{i = 1}^n [\gcd(i, k) = 1] f(n)=i=1n[gcd(i,k)=1]
很熟悉,因为它是每 k k k 个一循环,即
f ( n ) = ⌊ n k ⌋ φ ( k ) + f ( n   m o d   k ) f(n) = \left\lfloor\dfrac{n}{k}\right\rfloor \varphi(k) + f(n\bmod k) f(n)=knφ(k)+f(nmodk)
又因为 0 ≤ n   m o d   k < k 0\le n\bmod k < k 0nmodk<k,所以可以 O ( k log ⁡ k ) \Omicron(k\log k) O(klogk) 暴力预处理 f ( 0 ) ∼ f ( k ) f(0) \sim f(k) f(0)f(k) f ( k ) f(k) f(k) 直接当 φ ( k ) \varphi(k) φ(k) 用。

f f f 代回原式
a n s = ∑ d = 1 n [ gcd ⁡ ( d , k ) = 1 ] μ ( d ) ⌊ n d ⌋ f ( ⌊ m d ⌋ ) 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) ans=d=1n[gcd(d,k)=1]μ(d)dnf(dm)
后面两项打包整除分块,

因为 n ≤ 1 0 9 n\le 10^9 n109,明显需要杜教筛。

前面两项的前缀和
g ( n ) = ∑ i = 1 n [ gcd ⁡ ( i , k ) = 1 ] μ ( i ) g(n) = \sum_{i = 1}^n [\gcd(i, k) = 1] \mu(i) g(n)=i=1n[gcd(i,k)=1]μ(i)
用类似杜教筛的思路
g ( n ) = [ gcd ⁡ ( 1 , k ) = 1 ] g ( ⌊ n 1 ⌋ ) = ∑ i = 1 n [ gcd ⁡ ( i , k ) = 1 ] g ( ⌊ n i ⌋ ) − ∑ i = 2 n [ gcd ⁡ ( i , k ) = 1 ] g ( ⌊ n 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} g(n)=[gcd(1,k)=1]g(1n)=i=1n[gcd(i,k)=1]g(in)i=2n[gcd(i,k)=1]g(in)
而后面一项中的 [ gcd ⁡ ( i , k ) = 1 ] [\gcd(i, k) = 1] [gcd(i,k)=1] 的前缀和其实就是已经处理过的 f f f

所以只要处理出前面那一项就可以完全仿照杜教筛递归 + 记忆化计算了。
∑ i = 1 n [ gcd ⁡ ( i , k ) = 1 ] g ( ⌊ n i ⌋ ) = ∑ i = 1 n [ gcd ⁡ ( i , k ) = 1 ] ∑ j = 1 ⌊ n i ⌋ [ gcd ⁡ ( j , k ) = 1 ] μ ( j ) = ∑ i = 1 n ∑ j = 1 ⌊ n i ⌋ [ gcd ⁡ ( i j , k ) = 1 ] μ ( j ) = ∑ T = 1 n [ gcd ⁡ ( T , k ) = 1 ] ∑ d ∣ T μ ( d ) = ∑ T = 1 n [ gcd ⁡ ( T , k ) = 1 ] ( μ ∗ 1 ) ( T ) = ∑ T = 1 n [ gcd ⁡ ( T , k ) = 1 ] ε ( T ) = 1 \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} i=1n[gcd(i,k)=1]g(in)=i=1n[gcd(i,k)=1]j=1in[gcd(j,k)=1]μ(j)=i=1nj=1in[gcd(ij,k)=1]μ(j)=T=1n[gcd(T,k)=1]dTμ(d)=T=1n[gcd(T,k)=1](μ1)(T)=T=1n[gcd(T,k)=1]ε(T)=1
所以
g ( n ) = 1 − ∑ i = 2 n [ gcd ⁡ ( i , k ) = 1 ] g ( ⌊ n i ⌋ ) g(n) = 1 - \sum_{i = 2}^n [\gcd(i, k) = 1] g\left(\left\lfloor\dfrac{n}{i}\right\rfloor \right) g(n)=1i=2n[gcd(i,k)=1]g(in)
杜教筛即可做到 O ( n 2 3 ) \Omicron(n^{\frac{2}{3}}) O(n32) 计算。

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

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值