LOJ2085. 「NOI2016」循环之美
定义一个\(\frac{x}{y}\)是“纯循环小数”,当且仅当:化成小数之后,可以写成\(a.\dot{c_1}c_2c_3c_4\dots\dot{c_p}\)的形式。
问\(x\in[1,n]\),\(y\in[1,m]\),有多少对\((x,y),x \perp y\)满足\(\frac{x}{y}\)在\(k\)进制下是纯循环小数。
\(n,m\le 10^9,k\le 2000\)
分析条件:如果是,当且仅当\(\exist p>0,x\equiv k^px\pmod y\)。也就是\(k\perp y\)。
所以题目也就是要求\(\sum_{i=1}^m\sum_{j=1}^n [i\perp j][i\perp k]\)。
考虑拆式子。
说在前面:作为数论白痴,下面估的时间复杂度往往是不准确的。如果有大佬路过希望指教一下。
当拆\([i \perp j]\)时
经过推导得\(\sum_d [d\perp k]\mu(d)\lfloor\frac{n}{d}\rfloor\sum_{i=1}^{\lfloor\frac{m}{d}\rfloor}[i\perp k]\)。
令\(f(n)=\sum_{i=1}^n[i\perp k]=\lfloor\frac{n}{k}\rfloor\phi(k)+f(n\mod k)\)。可以快速求,不是复杂度瓶颈。
对于\(\sum_d [d\perp k]\mu(d)\lfloor\frac{n}{d}\rfloor f(\lfloor\frac{m}{d}\rfloor)\)整除分块,现在要求前缀和,即\(g(n,k)=\sum_{d=1}^n \mu(d)[d\perp k]\)
一种推法
把\([d\perp k]\)拆开推一波式子可以得到\(g(n,k)=\sum_{t|k}\mu(t)\sum_{d=1}^{\lfloor\frac{n}{t}\rfloor}\mu(dt)=\sum_{t|k}\mu(t)^2\sum_{d=1}^{\lfloor\frac{n}{t}\rfloor}\mu(d)[d\perp t]=\sum_{t|k}\mu(t)^2g(\lfloor\frac{n}{t}\rfloor,t)\)。整除分块搞下去。
边界是\(g(n,1)\)或\(g(0,k)\)。其中\(g(n,1)\)可以杜教筛求。
时间复杂度(记忆化):\(O(n^{2/3}+(k的因子个数)\sqrt n)\)。
另一种推法
设\(p\)为\(k\)的一个质因数,则\(k\)可以表示成\(p^tq\)的形式。
\(g(n,k)=\sum_{i=1}^n[i\perp q]\mu(i)-\sum_{p|i}[i\perp q]\mu(i)=g(n,q)+g(\lfloor\frac{n}{p^t}\rfloor,k)\)。
边界同上,时间复杂度(记忆化):\(O(n^{2/3}+(k的质因子个数)\sqrt n)\)
当拆\([i\perp k]\)时
设\(f(m,n,k)=\sum_{i=1}^m\sum_{j=1}^n [i\perp j][i\perp k]\)
经过推导得\(f(m,n,k)=\sum_{d|k}\mu(d)f(n,\lfloor\frac{m}{d}\rfloor,d)\)。
边界条件\(k=1\),化一下得\(\sum_d \mu(d)\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor\)。整除分块,杜教筛算\(\mu\)的前缀和。
这个东西不太会分析,但是实践表明递归的次数不是很多(没有对\(f\)进行记忆化的情况下)。杜教筛记忆化\(\lfloor\frac{n}{d}\rfloor\)或\(\lfloor\frac{m}{d}\rfloor\)这样的前缀和,总时间\(O(n^{2/3}+m^{2/3})\)。整除分块的时间复杂度大概是\(O(\sqrt{\max(n,m)}\)。总的时间复杂度不会算……
代码是最后一种做法。
using namespace std;
#include <bits/stdc++.h>
#define ll long long
//#define ll __int128
#define N 6000005
int gcd(int a,int b){
int k;
while (b)
k=a%b,a=b,b=k;
return a;
}
int n,m,k;
int p[N],np;
bool inp[N];
int mu[N],mnp[N];
int smu[N];
void init(int n=6000000){
mu[1]=1;
for (int i=2;i<=n;++i){
if (!inp[i])
p[++np]=i,mnp[i]=i,mu[i]=-1;
for (int j=1;j<=np && i*p[j]<=n;++j){
inp[i*p[j]]=1;
mnp[i*p[j]]=p[j];
if (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];
}
const int EMP=-1926081700;
struct memory{
int n;
ll v0[N],v1[N];
void init(int _n){
n=_n;
for (int i=1;i*i<=n;++i)
v0[i]=v1[i]=EMP;
}
ll &operator[](int x){return (ll)x*x<=n?v0[x]:v1[n/x];}
} bzn,bzm;
ll S1(int n,memory &bzn){
if (n<=6000000)
return smu[n];
if (bzn[n]!=EMP)
return bzn[n];
ll sum=0;
for (int i=2;i<=n;){
int d=n/i,j=n/d;
sum+=(ll)(j-i+1)*S1(d,bzn);
i=j+1;
}
return bzn[n]=1-sum;
}
ll S2(int m,int n,int rev){
if (rev)
swap(m,n);
ll sum=0,pre=0;
for (int i=1;i<=m && i<=n;){
int dn=n/i,dm=m/i,j=min(n/dn,m/dm);
ll tmp=S1(j,n/dn<m/dm?bzn:bzm);
sum+=(ll)(tmp-pre)*dn*dm;
pre=tmp;
i=j+1;
}
return sum;
}
ll f(int m,int n,int k,int rev){
if (m==0 || n==0)
return 0;
ll s=0;
if (k==1){
s+=S2(m,n,rev);
return s;
}
int i=1;
for (;i*i<k;++i)
if (k%i==0){
if (mu[i])
s+=mu[i]*f(n,m/i,i,rev^1);
if (mu[k/i])
s+=mu[k/i]*f(n,m/(k/i),k/i,rev^1);
}
if (i*i==k && mu[i])
s+=mu[i]*f(n,m/i,i,rev^1);
return s;
}
int main(){
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
init();
scanf("%d%d%d",&n,&m,&k);
bzn.init(n);
bzm.init(m);
printf("%lld\n",(long long)f(m,n,k,0));
// printf("%lld\n",cnt);
return 0;
}