[bzoj4652] [NOI2016]迴圈之美
Description
牛牛是一個熱愛演算法設計的高中生。在他設計的演算法中,常常會使用帶小數的數進行計算。牛牛認為,如果在 k
進位制下,一個數的小數部分是純迴圈的,那麼它就是美的。現在,牛牛想知道:對於已知的十進位制數 n 和 m,在
kk 進位制下,有多少個數值上互不相等的純迴圈小數,可以用分數 xy 表示,其中 1≤x≤n,1≤y≤m,且 x,y是整數
。一個數是純迴圈的,當且僅當其可以寫成以下形式:a.c1˙c2c3…cp-1cp˙其中,a 是一個整數,p≥1;對於 1
≤i≤p,ci是 kk 進位制下的一位數字。例如,在十進位制下,0.45454545……=0.4˙5˙是純迴圈的,它可以用 5/11
、10/22 等分數表示;在十進位制下,0.1666666……=0.16˙則不是純迴圈的,它可以用 1/6 等分數表示。需要特
別注意的是,我們認為一個整數是純迴圈的,因為它的小數部分可以表示成 0 的迴圈或是 k?1 的迴圈;而一個小
數部分非 0 的有限小數不是純迴圈的。
Input
只有一行,包含三個十進位制數N,M,K意義如題所述,保證 1≤n≤10^9,1≤m≤10^9,2≤k≤2000
Output
一行一個整數,表示滿足條件的美的數的個數。
Sample Input
2 6 10
Sample Output
4
Solution
\(k\)進位制下\(x/y\)為迴圈小數,即:
\[ \big[\frac{xk^l}{y}\big]=\big[\frac{x}{y}\big] \]
其中中括號表示小數部分,\(l\)是\(k\)進位制下迴圈節長度。
中括號展開可得:
\[ \frac{xk^l}{y}-\lfloor\frac{xk^l}{y}\rfloor=\frac{x}{y}-\lfloor\frac{x}{y}\rfloor\\ xk^l-y\lfloor\frac{xk^l}{y}\rfloor=x-y\lfloor\frac{x}{y}\rfloor \]
考慮取整那一項不好處理,可以玩一波騷操作,兩邊對\(y\)取模:
\[ xk^l\equiv x\pmod{y} \]
因為\((x,y)=1\),所以兩邊除掉:
\[ k^l\equiv1\pmod{y} \]
可得:
\[ (k,y)=1 \]
所以答案可以形式化的寫成:
\[ \sum_{i=1}^n\sum_{j=1}^m[(i,j)=1][(j,k)=1] \]
然後嘗試著對\([(i,j)=1]\)進行莫比烏斯反演:
\[ \begin{align} ans=&\sum_{i=1}^n\sum_{j=1}^m[(j,k)=1]\sum_{d|i\&d|j}\mu(d)\\ =&\sum_{d=1}^{\min(n,m)}\mu(d)[(d,k)=1]\lfloor\frac{n}{d}\rfloor\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}[(j,k)=1] \end{align} \]
此處省略了中間交換求和符號之類的過程。
然後考慮下這個式子後面一塊,設:
\[ f(n)=\sum_{i=1}^n[(i,k)=1] \]
考慮分成幾塊,每塊長度都是\(k\),顯然:
\[ f(n)=\lfloor\frac{n}{k}\rfloor f(k)+f(n\bmod k) \]
再考慮下答案的前半段,由於後兩項要數論分塊,所以要求前兩項的字首和,設:
\[ g(n,k)=\sum_{d=1}^n\mu(d)[(d,k)=1] \]
對後面莫比烏斯反演下:
\[ \begin{align} g(n,k)=&\sum_{d=1}^n\mu(d)\sum_{t|d\&t|k}\mu(t)\\ =&\sum_{t=1}^n\mu(t)[t|k]\sum_{d=1}^{\lfloor\frac{n}{t}\rfloor}\mu(dt) \end{align} \]
然後最玄學的一步來了,,若想要使\(\mu(dt)\ne 0\),顯然\((d,t)=1\),此時\(\mu(dt)=\mu(d)\mu(t)\),所以後面一項可以寫成:
\[ \begin{align} g(n,k)=&\sum_{t=1}^n\mu(t)[t|k]\sum_{d=1}^{\lfloor\frac{n}{t}\rfloor}\mu(d)\mu(t)[(d,t)=1]\\ =&\sum_{t=1}^n\mu^2(t)[t|k]\sum_{d=1}^{\lfloor\frac{n}{t}\rfloor}\mu(d)[(d,t)=1]\\ =&\sum_{t=1}^n\mu^2(t)[t|k]g(\lfloor\frac{n}{d}\rfloor,t)\\ =&\sum_{t|k}\mu^2(t)g(\lfloor\frac{n}{d}\rfloor,t)\\ \end{align} \]
這是個遞迴的形式,預處理下\(k\)以內所有數的約數,直接記憶化暴力算就好了。
對於\(k=1\)的情況,\(g\)就變成了\(\sum_{i=1}^n \mu(i)\),這個可以杜教篩出來。
答案可以寫成:
\[ ans=\sum_{d=1}^{\min(n,m)}\mu(d)[(d,k)=1]\lfloor\frac{n}{d}\rfloor f(\lfloor\frac{m}{d}\rfloor) \]
後面數論分塊就行了。
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar() ((p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin)),p1==p2)?EOF:*p1++)
#endif
namespace fast_IO {
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T> inline void read(T &x) {
x=0;T f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}
template <typename T,typename... Args> inline void read(T& x,Args& ...args) {
read(x),read(args...);
}
char buf2[1<<21],a[80];int p,p3=-1;
inline void flush() {fwrite(buf2,1,p3+1,stdout),p3=-1;}
template <typename T> inline void write(T x) {
if(p3>(1<<20)) flush();
if(x<0) buf2[++p3]='-',x=-x;
do {a[++p]=x%10+48;} while(x/=10);
do {buf2[++p3]=a[p];} while(--p);
buf2[++p3]='\n';
}
template <typename T,typename... Args> inline void write(T x,Args ...args) {
write(x),write(args...);
}
}
using fast_IO :: read;
using fast_IO :: write;
using fast_IO :: flush;
#define ll long long
const int maxn = 5e3+10;
const int N = 5e6+10;
int pri[N],tot,mu[N],vis[N],F[maxn],Mu[N];
void sieve() {
mu[1]=1;
for(int i=2;i<N;i++) {
if(!vis[i]) pri[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*pri[j]<N;j++) {
vis[i*pri[j]]=1;
if(i%pri[j]==0) break;
mu[i*pri[j]]=-mu[i];
}
}
for(int i=1;i<N;i++) Mu[i]=Mu[i-1]+mu[i];
}
map<int ,int > mp;
int sum_mu(int n) {
if(n<N) return Mu[n];
if(mp[n]) return mp[n];
int res=1,T=2;
while(T<=n) {
int pre=T;T=n/(n/T);
res=res-(T-pre+1)*sum_mu(n/T);T++;
}
return mp[n]=res;
}
int dv[2001][201];
map<int ,int > G[2001];
int g(int n,int k) {
if(k==1) return sum_mu(n);int ans=0;
if(G[k][n]) return G[k][n];
if(!n) return 0;
for(int i=1;i<=dv[k][0];i++)
ans+=abs(mu[dv[k][i]])*g(n/dv[k][i],dv[k][i]);
return G[k][n]=ans;
}
int n,m,k;
int f(int x) {return F[k]*(x/k)+F[x%k];}
int main() {
read(n,m,k);
for(int i=1;i<=k;i++) F[i]=F[i-1]+(__gcd(i,k)==1);
for(int i=1;i<=k;i++)
for(int j=1;j<=i;j++)
if(i%j==0) dv[i][++dv[i][0]]=j;
sieve();int T=1;ll ans=0;
while(T<=n&&T<=m) {
int pre=T;T=min(n/(n/T),m/(m/T));
ans=ans+1ll*(g(T,k)-g(pre-1,k))*(n/T)*f(m/T);T++;
}write(ans);
flush();
return 0;
}