P1587

[NOI2016] 循环之美

首先是这个小结论,满足什么条件这个小数才是纯循环的?

我们设循环节长度为 $z$,那么我们有如下结论:

$$
\dfrac {x k ^ z } { y } - \left\lfloor \dfrac { x k ^ z } { y } \right\rfloor = \dfrac { x } { y } - \left\lfloor \dfrac { x } { y } \right\rfloor
$$

意思就是,这两个数的小数部分是相同的。

换句话说,上面的式子实际上使 $x k ^ z $ 满足了:

$$ x k ^ z \equiv x \pmod { y }$$

首先题目要求我们求的是最简分数,所以说必然有 $x \perp y$,因此我们这个式子可以两边同时乘上 $x ^ { - 1 }$。

好,现在我们就有了条件:

$$ k ^ z \equiv 1 \pmod {y}$$

我来 yy 一种奇怪的解释。

上式说明,我们必然存在一个 $x$ 使得:

$$k ^ z x \equiv 1 \pmod { y }$$

根据同余方程的那一套理论,我们可以知道当且仅当 $k ^ z \perp y$ 的时候方程有解。

因此必然会有 $y \perp k$,实际上这就是纯循环的充要条件。

现在我们找到两个条件,不难发现我们答案实际上就是:

$$
\begin{aligned}
& \sum _ { x = 1 } ^ { n } \sum _{ y =1 } ^ { m } [ x \perp y ] [ y \perp k ]
\\
= & \sum _ { x = 1 } ^ { n } \sum _{ y =1 } ^ { m } \sum _{ d \mid y , d \mid x} \mu (d) [ y \perp k ]
\\
= & \sum _ { d = 1 } ^ { \min \{ n , m \} } \mu ( d ) \sum _{ x = 1 } ^ { \lfloor n / d \rfloor} \sum _ { y = 1 } ^ { \lfloor m / d \rfloor} [ yd \perp k ]
\\
= & \sum _ { d = 1 } ^ { \min \{ n , m \} } \mu ( d ) [ d \perp k ] \left \lfloor \dfrac { n } { d } \right \rfloor \sum _ { y = 1 } ^ { \lfloor m / d \rfloor} [ y \perp k ]
\end{aligned}
$$

现在这个东西就可以数论分块 $d$ 这一维,然后尝试快速处理后面的 $y$ 的求和。

尝试抽象出两个函数:

$$g ( n , k ) = \sum _ { d = 1 } ^ { n } \mu ( d ) [ d \perp k ]$$

$$f ( n , k ) = \sum _ { d = 1 } ^ { n } [ d \perp k ]$$

我们发现 $ f ( n , 1 )$ 是易求的,而 $g ( n , 1 )$ 通过杜教筛也是易求的。

考虑类似 Min-25 筛的方法,通过素数来递归求值。

发现我们的 $k$ 的平方因子在互质条件中都是多余的,因此我们把 $k$ 化简素数幂都为 1 的形式。

这样我们考虑一个素数 $p \mid k$,先从 $f$ 开始:

$$
\begin{aligned}
f ( n , k ) = & \sum _ {d = 1 } ^ { n } [ d \mid k ]
\\
= & \sum _ { d = 1 } ^ { n } [ d \mid ( k / p )] - \sum _ { d = 1 } ^ { n } [ d \mid (k/p) ] [ p \mid d]
\\
= & f ( n , k/p) - \sum _ {d = 1 } ^ {\lfloor n/p \rfloor} [ dp \mid ( k / p ) ]
\\
= & f ( n , k/p) - \sum _ {d = 1 } ^ {\lfloor n/p \rfloor} [ d \mid ( k / p ) ]
\\
= & f ( n , k / p ) - f ( \lfloor n / p \rfloor , k / p )
\end{aligned}
$$

然后再来看 $g$ 的推导:

$$
\begin{aligned}
g ( n , k ) = & \sum _ { d = 1 } ^ { n } \mu ( d ) [ d \perp k]
\\
= & \sum _ { d = 1 } ^ { n } \mu ( d ) [ d \perp ( k / p ) ] - \sum _ { d = 1 } ^ { n } \mu ( d ) [ d \perp ( k / p ) ] [ p \perp d ]
\\
= & g ( n , k / p ) - \sum _ { d = 1 } ^ { \lfloor n / p \rfloor} \mu ( d p ) [ d p \perp ( k / p ) ]
\\
= & g ( n , k / p ) - \mu ( p ) \sum _ { d = 1 } ^ { \lfloor n / p \rfloor} \mu ( d ) [ d \perp p] [ d \perp ( k / p ) ]
\\
= & g ( n , k / p ) + \sum _ { d = 1 } ^ { \lfloor n / p \rfloor} \mu ( d ) [ d \perp k ]
\\
= & g (n , k / p ) + g ( \lfloor n / p \rfloor , k)
\end{aligned}
$$

然后这玩意就可以递归计算了。

关于这个复杂度。

每次递推是 $ O( 1 ) $ 的(虽然我写的只是近似 $ O( 1 ) $)。

考虑到 $f,g$ 的第一位实际上是被数论分块过的,所以说仅仅只有 $O(\sqrt{n})$ 个起始递推。

然后这个递推的层数是 $k$ 的素因子个数,其级别为 $O(\log k / \log \log k)$。

因此递推的复杂度是 $ O(\sqrt {n} \log k / \log \log k)$ 的。

然后这个杜教筛的总复杂度是 $ O( n ^ {2 / 3 })$ 的,因为上界没变。。。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<iostream>
#include<cstdio>
#include<map>
#define ll long long
using namespace std;
namespace Ehnaev{
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}
inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}
else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}
while(len>=0) putchar(buf[len--]);
}
}using Ehnaev::read;using Ehnaev::write;

const ll N=1e6,M=2e3;

ll n,m,k,cnt,amt;
ll mu[N+5],smu1[N+5],prime[N+5],pk[M+5];
bool f[N+5];
map<ll,ll> smu2,remf[M+5],remg[M+5];

inline ll Smu(ll x) {
if(x<=N) return smu1[x];
if(smu2.find(x)!=smu2.end()) return smu2[x];
ll tmp=1;
for(ll i=2,j;i<=x;i=j+1) {
j=x/(x/i);tmp-=(j-i+1)*Smu(x/i);
}
return smu2[x]=tmp;
}

inline void Init() {
f[1]=1;mu[1]=1;
for(ll i=2;i<=N;i++) {
if(!f[i]) {prime[++cnt]=i;mu[i]=-1;}
for(ll j=1;j<=cnt&&prime[j]*i<=N;j++) {
f[i*prime[j]]=1;
if(i%prime[j]==0) {mu[i*prime[j]]=0;break;}
mu[i*prime[j]]=-mu[i];
}
}
for(ll i=1;i<=N;i++) {smu1[i]=smu1[i-1]+mu[i];}
for(ll i=2;i*i<=k;i++) {
if(k%i==0) {pk[++amt]=i;while(k%i==0) k/=i;}
}
if(k>1) pk[++amt]=k;k=1;
for(ll i=1;i<=amt;i++) k=k*pk[i];
}

inline ll F(ll n,ll k) {
if(n==0) return 0;if(n==1) return 1;
if(k==1) return n;
if(remf[k].find(n)!=remf[k].end()) return remf[k][n];
for(ll i=1;i<=amt;i++) {
if(k%pk[i]==0) {return remf[k][n]=F(n,k/pk[i])-F(n/pk[i],k/pk[i]);}
}
return 0;
}

inline ll G(ll n,ll k) {
if(n==0) return 0;if(n==1) return 1;
if(k==1) return Smu(n);
if(remg[k].find(n)!=remg[k].end()) return remg[k][n];
for(ll i=1;i<=amt;i++) {
if(k%pk[i]==0) {return remg[k][n]=G(n,k/pk[i])+G(n/pk[i],k);}
}
return 0;
}

int main() {

n=read();m=read();k=read();Init();

ll ans=0;
for(ll i=1,j;i<=n&&i<=m;i=j+1) {
j=min(n/(n/i),m/(m/i));
ll tmp=G(j,k)-G(i-1,k);if(tmp==0) continue;
ans+=tmp*(n/i)*F(m/i,k);
}

write(ans);

return 0;
}