0%

「NOI2016」循环之美

\(k\) 进制下可以表示为纯循环小数(认为整数也是)的既约分数 \(\dfrac{i}{j}\),且 \(1\leqslant i\leqslant n\)\(1\leqslant j\leqslant m\) 的个数。\(n,m\leqslant 10^9\)\(k\leqslant 2000\)

经过尝试可以发现,既约分数 \(\dfrac{i}{j}\)\(k\) 进制下是纯循环的当且仅当 \(\gcd(j,k)=1\)(证明可以见其它题解)。

所以,我们只需要求 \[ \sum\limits_{i=1}^n\sum\limits_{j=1}^m[\gcd(i,j)=1][\gcd(j,k)=1] \] 因为要既约分数,所以要保证 \(\gcd(i,j)=1\)

因为两个 \(\gcd\) 都与 \(j\) 有关,我们考虑把 \(j\) 提到外面 \[ ans=\sum\limits_{j=1}^m[\gcd(j,k)=1]\sum\limits_{i=1}^n[\gcd(i,j)=1] \] 然后我们对后面来一次莫比乌斯反演 \[ \begin{aligned} ans&=\sum\limits_{j=1}^m[\gcd(j,k)=1]\sum\limits_{i=1}^n\sum\limits_{d\mid i,d\mid j}\mu(d)\\ &=\sum\limits_{j=1}^m[\gcd(j,k)=1]\sum\limits_{d\mid j}\lfloor\frac{n}{d}\rfloor\mu(d) \end{aligned} \] 然后我们把 \(d\) 提到前面去 \[ ans=\sum\limits_{d=1}^n\lfloor\frac{n}{d}\rfloor\mu(d)\sum\limits_{d\mid j,j\leqslant m}[\gcd(j,k)=1] \] 我们发现,\(j\) 可以表示为 \(xd\),那么 \(\gcd(j,k)=1\) 等价于 \(\gcd(x,k)=1\)\(\gcd(d,k)=1\) ,于是我们有 \[ ans=\sum\limits_{d=1}^n\lfloor\frac{n}{d}\rfloor\mu(d)[\gcd(d,k)=1]\sum\limits_{x=1}^{\lfloor\frac{m}{d}\rfloor}[\gcd(x,k)=1] \] 然后我们观察第二个求和号,如果我们设 \(f(z)=\sum\limits_{i=1}^z[\gcd(i,k)=1]\),那么后面的就是 $ f()$。

我们考虑是否能够快速计算 \(f\),根据辗转相除(减)法的原理,我们有 \(\gcd(x,k)=\gcd(x+k,k)\),所以我们有 \[ f(z)=\lfloor \frac{z}{k}\rfloor f(k)+f(z\bmod k) \] 即我们按照每个数模 \(k\) 的余数将其分为若干等价类。这样我们只需要预处理 \(f(1)\dots f(k)\) 就可以在 \(O(1)\) 时间内计算 \(f(z)\)

那么有 \[ ans=\sum\limits_{d=1}^n\lfloor\frac{n}{d}\rfloor f(\lfloor\frac{m}{d}\rfloor)\mu(d)[\gcd(d,k)=1] \] 我们发现,如果我们可以快速计算 \(\mu(d)[\gcd(d,k)=1]\) 的前缀和,我们就可以通过整除分块计算答案,那么问题就转化为了快速计算 \(\mu(d)[\gcd(d,k)=1]\) 的前缀和。

我们设 \(g(z)=\sum\limits_{d=1}^z\mu(d)[\gcd(d,k)=1]\),那么我们再来一次莫比乌斯反演: \[ \begin{aligned} g(z)&=\sum\limits_{d=1}^z\mu(d)\sum\limits_{t\mid d,t\mid k}\mu(t)\\ &=\sum\limits_{t\mid k}\mu(t)\sum\limits_{t\mid d,d\leqslant z}\mu(d)\\ &=\sum\limits_{t\mid k}\mu(t)\sum\limits_{d=1}^{\lfloor\frac{z}{t}\rfloor}\mu(dt)\\ \end{aligned} \] 我们发现,如果 \(\gcd(d,t)\not= 1\),那么 \(dt\) 一定含有平方因子(\(\gcd(d,t)^2\)),所以这时 \(\mu(dt)=0\),由于我们只在乎和,所以我们可以只对 \(\mu(dt)\not=0\),即 \(\gcd(d,t)=1\) 的情况计算。那么有: \[ g(z)=\sum\limits_{t\mid k}\mu(t)\sum\limits_{d=1}^{\lfloor\frac{z}{t}\rfloor}\mu(dt)[\gcd(d,t)=1] \] 这样由于我们强制令 \(d,t\) 互质,我们就可以把 \(\mu(dt)\) 拆成 \(\mu(d)\mu(t)\)\[ \begin{aligned} g(z)&=\sum\limits_{t\mid k}\mu(t)\sum\limits_{d=1}^{\lfloor\frac{z}{t}\rfloor}\mu(d)\mu(t)[\gcd(d,t)=1]\\ &=\sum\limits_{t\mid k}\mu^2(t)\sum\limits_{d=1}^{\lfloor\frac{z}{t}\rfloor}\mu(d)[\gcd(d,t)=1] \end{aligned} \] 我们发现第二个求和号后面的东西和 \(g\) 很像,不过是 \([\gcd(d,t)=1]\) 而不是 \([\gcd(d,k)=1]\),为此我们扩展一下 \(g\) 的定义,设 \(g(z,c)=\sum\limits_{i=1}^z\mu(i)[\gcd(i,c)=1]\),那么 \[ g(z,c)=\sum\limits_{t\mid c}\mu^2(t)g(\lfloor\frac{z}{t}\rfloor,t) \] 边界条件为 \(c=1\),此时就是莫比乌斯函数的前缀和,可以杜教筛求解。注意计算时要记忆化,复杂度据说是 \(O(n^{\frac{2}{3}}+\sigma_0(k)\sqrt n)\) 的,不会证。

代码:

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int const N=1000005;
int k,p[N+5],mu[N+5],smu[N+5],cnt,F[2005];
bool flag[N+5];
vector<int>v[2005];
int gcd(int x,int y){return y?gcd(y,x%y):x;}
map<pair<int,int>,int>mp;
map<int,int>mp2;
int s(int n)
{
if(n<=N)return smu[n];
if(mp2.find(n)!=mp2.end())return mp2[n];
int ans=1;
for(int l=2,r;l<=n;l=r+1)
r=(n/(n/l)),ans-=(r-l+1)*s(n/l);
return mp2[n]=ans;
}
int f(int n){return (n/k)*F[k]+F[n%k];}
int g(int z,int c)
{
if(c==1)return s(z);
if(!z)return 0;
if(mp.find(make_pair(z,c))!=mp.end())return mp[make_pair(z,c)];
int ans=0;
for(auto i:v[c])
if(mu[i])ans+=g(z/i,i);
return mp[make_pair(z,c)]=ans;
}
int main()
{
mu[1]=1;
for(int i=2;i<=N;i++)
{
if(!flag[i]){p[++cnt]=i;mu[i]=-1;}
for(int j=1;j<=cnt&&i*p[j]<=N;j++)
{
flag[i*p[j]]=1;
if(!(i%p[j])){mu[i*p[j]]=0;break;}
mu[i*p[j]]=-mu[i];
}
}
for(int i=1;i<=N;i++)smu[i]=smu[i-1]+mu[i];
int n,m;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=k;i++)F[i]=F[i-1]+(gcd(k,i)==1);
for(int i=1;i<=k;i++)
for(int j=i;j<=k;j+=i)
v[j].push_back(i);
long long 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)*f(m/l)*(g(r,k)-g(l-1,k));
}
printf("%lld\n",ans);
return 0;
}