bzoj4652 [Noi2016]循环之美
Description
Input
Output
Sample Input
Sample Output
explanation
满足条件的数分别是:
1/1=1.0000……
1/3=0.3333……
2/1=2.0000……
2/3=0.6666……
1/1 和 2/2 虽然都是纯循环小数,但因为它们相等,因此只计数一次;同样,1/3 和 2/6 也只计数一次。
正解:莫比乌斯函数+杜教筛。
首先,小学奥数学过一个东西。一个分数是纯循坏小数,当且仅当这个分数的分母没有2或5作为因子,而10分解以后正好是2和5。那么我们类比一下:k进制分数是纯循环小数是不是就是分母没有k的因数。也就是说,k和分母要互质。
所以我们要求的就是:$Ans=\sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i,j)==1,\gcd(j,k)==1]$
然后一脸懵逼,果断24分暴力。(套路用不上了。。)
24分暴力:
1 //It is made by wfj_2048~ 2 #include <algorithm> 3 #include <iostream> 4 #include <complex> 5 #include <cstring> 6 #include <cstdlib> 7 #include <cstdio> 8 #include <vector> 9 #include <cmath> 10 #include <queue> 11 #include <stack> 12 #include <map> 13 #include <set> 14 #define inf (1<<30) 15 #define il inline 16 #define RG register 17 #define ll long long 18 19 using namespace std; 20 21 int n,m,k,ans; 22 23 il int gi(){ 24 RG int x=0,q=1; RG char ch=getchar(); while ((ch<'0' || ch>'9') && ch!='-') ch=getchar(); 25 if (ch=='-') q=-1,ch=getchar(); while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; 26 } 27 28 il int gcd(RG int a,RG int b){ return b ? gcd(b,a%b) : a; } 29 30 il void work(){ 31 n=gi(),m=gi(),k=gi(); 32 for (RG int i=1;i<=m;++i){ 33 if (gcd(i,k)!=1) continue; 34 for (RG int j=1;j<=n;++j) if (gcd(i,j)==1) ans++; 35 } 36 printf("%d\n",ans); return; 37 } 38 39 int main(){ 40 work(); 41 return 0; 42 }
其实上式是可以化简的,我们考虑如何化简这个式子。
$Ans=\sum_{i=1}^{m}[\gcd(i,k)==1]\sum_{j=1}^{n}[\gcd(i,j)==1]$
$Ans=\sum_{i=1}^{m}[gcd(i,k)==1]\sum_{j=1}^{n}\sum_{d|i,d|j}\mu(d)$
$Ans=\sum_{i=1}^{m}[\gcd(i,k)==1]\sum_{d|i}^{n}\mu(d)\left \lfloor \frac{n}{d} \right \rfloor$
其实到这里,就是莫比乌斯函数的24分暴力了。。但这不是跟$O(n^{2})$暴力一样的分吗。。
我们把$\mu$提前枚举,那么$Ans=\sum_{d=1}^{min(n,m)} \mu(d)\left \lfloor \frac{n}{d} \right \rfloor \sum_{i=1}^{m} [d|i,\gcd(i,k)==1]$
$Ans=\sum_{d=1}^{min(n,m)} \mu(d)\left \lfloor \frac{n}{d} \right \rfloor \sum_{i=1}^{\frac{m}{d}} [\gcd(i*d,k)==1]$
$Ans=\sum_{d=1}^{min(n,m)} [\gcd(d,k)==1]\mu(d)\left \lfloor \frac{n}{d} \right \rfloor \sum_{i=1}^{\frac{m}{d}} [\gcd(i,k)==1]$
我们设$f(n)=\sum_{i=1}^{n}[\gcd(i,k)==1]$。根据欧几里得定理,$f(n)=\left \lfloor \frac{n}{k} \right \rfloor f(k)+f(n \bmod k)$
于是我们预处理出$f$在$[1,k]$的取值,就能把复杂度降到$O(nlogn)$,实际上可以过84分,相当于$O(n)$的复杂度。
84分暴力:
1 //It is made by wfj_2048~ 2 #include <algorithm> 3 #include <iostream> 4 #include <complex> 5 #include <cstring> 6 #include <cstdlib> 7 #include <cstdio> 8 #include <vector> 9 #include <cmath> 10 #include <queue> 11 #include <stack> 12 #include <map> 13 #include <set> 14 #define inf (1<<30) 15 #define N (20000010) 16 #define il inline 17 #define RG register 18 #define ll long long 19 20 using namespace std; 21 22 int f[2010],vis[N],mu[N],prime[N],n,m,k,nn,cnt; 23 ll ans; 24 25 il int gi(){ 26 RG int x=0,q=1; RG char ch=getchar(); while ((ch<'0' || ch>'9') && ch!='-') ch=getchar(); 27 if (ch=='-') q=-1,ch=getchar(); while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; 28 } 29 30 il int gcd(RG int a,RG int b){ return b ? gcd(b,a%b) : a; } 31 32 il void pre(){ 33 vis[1]=mu[1]=1; 34 for (RG int i=2;i<=nn;++i){ 35 if (!vis[i]) mu[i]=-1,prime[++cnt]=i; 36 for (RG int j=1,k=i*prime[j];j<=cnt && k<=nn;++j,k=i*prime[j]){ 37 vis[k]=1; if (i%prime[j]) mu[k]=-mu[i]; else break; 38 } 39 } 40 for (RG int i=1;i<=k;++i) f[i]=f[i-1]+(gcd(i,k)==1); return; 41 } 42 43 il void work(){ 44 n=gi(),m=gi(),k=gi(),nn=min(n,m); pre(); 45 for (RG int d=1;d<=nn;++d){ 46 if (gcd(d,k)!=1) continue; 47 ans+=(ll)mu[d]*(ll)(n/d)*(ll)((ll)(m/d/k)*(ll)f[k]+(ll)f[m/d%k]); 48 } 49 printf("%lld\n",ans); return; 50 } 51 52 int main(){ 53 work(); 54 return 0; 55 }
100分:
自己写不动了。。LCF学长的题解写得挺好的:http://www.cnblogs.com/lcf-2000/p/6250330.html
摘自学长博客:
我们考虑接下来该如何优化。由于$\lfloor \frac{m}{x} \rfloor$只有$\sqrt{m}$种取值,$\lfloor \frac{n}{x} \rfloor$只有$\sqrt{n}$种取值,于是我们显然可以分段求和。然后,我们就需要快速求出$\sum_{i=1}^n[i\perp k]\mu(i)$的值。
不妨设$g(n,k)=\sum_{i=1}^n[i\perp k]\mu(i)$,我们来考虑一下这个函数如何快速求。我们先考虑$k$的一个质因数$p$,那么$k$显然可以写成$p^cq$的形式。由于在$[1,n]$的范围中只有与$k$互质的才是有效值,那么若$x\perp k$,我们可以得到$x\perp p$并且$x\perp q$。于是,我们可以考虑从$x\perp q$的取值中减去$x$不与$p$互质的部分,就可以得到$x\perp k$的部分。这里如果不懂的话可以自己画一个$x\perp q$,$x\perp p$,$x\perp k$的关系图理解一下。
由于所有与$q$互质的数一定可以写成$p^xy(y\perp q)$的形式。那么我们需要减去的数一定满足$x>0$。又由于当$x>1$时$\mu(p^xy)=0$,所以我们只需要考虑$x=1$的情况即可。在这种情况下,我们需要考虑的数就是$py(y\perp q)$的形式,所以我们可以得到如下式子:\begin{aligned} g(n,k)&=\sum_{i=1}^n[i\perp q]\mu(i)-\sum_{y=1}^{\lfloor\frac{n}{p}\rfloor}[py\perp q]\mu(py) \\&=g(n,q)- \sum_{y=1}^{\lfloor\frac{n}{p}\rfloor}[y\perp q]\mu(py)\end{aligned}
上面的最后一步是由于$p\perp q$,所以$py\perp q$只需在保证$y\perp q$即可。
我们接着来考虑后一个式子。显然当$p\perp y$的时候$\mu(py)=\mu(p)\mu(y)$,否则$\mu(py)=0$。于是,我们又得到了:
\begin{aligned} g(n,k)&=g(n,q)- \sum_{y=1}^{\lfloor\frac{n}{p}\rfloor}[y\perp p][y\perp q]\mu(p)\mu(y)\\&=g(n,q)-\mu(p)\sum_{y=1}^{\lfloor\frac{n}{p}\rfloor}[y\perp k]\mu(y)\\&=g(n,q)+g(\lfloor\frac{n}{p}\rfloor,k) \end{aligned}
于是我们就可以递归求解了。容易发现边界情况就是$n=0$或者$k=1$。$n=0$的时候直接返回$0$就可以了,$k=1$的时候就是莫比乌斯函数的前缀和,用杜教筛求出来就可以了。由于每次递归要么$n$会变成$\lfloor \frac{n}{p} \rfloor$,有$O(\sqrt{n})$种取值;要么$p$少了一个质因数,有$\omega(k)$种取值,所以总共有$O(\omega(k)\sqrt{n})$种取值,记忆化搜索即可。其中$\omega(k)$表示$k$的不同的质因子数目。于是最后的总复杂度为$O(\omega(k)\sqrt{n}+n^{\frac{2}{3}})$,可以通过此题。
我也不写哈希了,直接强行$map$就好。。
1 //It is made by wfj_2048~ 2 #include <algorithm> 3 #include <iostream> 4 #include <complex> 5 #include <cstring> 6 #include <cstdlib> 7 #include <cstdio> 8 #include <vector> 9 #include <cmath> 10 #include <queue> 11 #include <stack> 12 #include <map> 13 #include <set> 14 #define inf (1<<30) 15 #define N (5000010) 16 #define il inline 17 #define RG register 18 #define ll long long 19 #define calc(x) ( (x/k)*f[k]+f[x%k] ) 20 21 using namespace std; 22 23 map <ll,map<ll,ll> >gg; 24 map <ll,ll> ff; 25 26 ll f[2010],di[2010],vis[N],mu[N],prime[N],n,m,k,nn,NN,pos,cnt,kcnt,ans; 27 28 il ll gi(){ 29 RG ll x=0,q=1; RG char ch=getchar(); while ((ch<'0' || ch>'9') && ch!='-') ch=getchar(); 30 if (ch=='-') q=-1,ch=getchar(); while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; 31 } 32 33 il ll gcd(RG ll a,RG ll b){ return b ? gcd(b,a%b) : a; } 34 35 il void pre(){ 36 vis[1]=mu[1]=1; RG ll kk=k; 37 for (RG ll i=2;i<=nn;++i){ 38 if (!vis[i]) mu[i]=-1,prime[++cnt]=i; 39 for (RG ll j=1,k=i*prime[j];j<=cnt && k<=nn;++j,k=i*prime[j]){ 40 vis[k]=1; if (i%prime[j]) mu[k]=-mu[i]; else break; 41 } 42 } 43 for (RG ll i=2;i<=k;++i){ 44 if (!(kk%i)) di[++kcnt]=i; 45 while (!(kk%i)) kk/=i; 46 } 47 for (RG ll i=1;i<=k;++i) f[i]=f[i-1]+(gcd(i,k)==1); 48 for (RG ll i=2;i<=nn;++i) mu[i]+=mu[i-1]; return; 49 } 50 51 il ll du(RG ll x){ 52 if (x<=nn) return mu[x]; 53 if (ff[x]) return ff[x]; 54 RG ll ans=1,pos; 55 for (RG ll i=2;i<=x;i=pos+1) 56 pos=x/(x/i),ans-=(pos-i+1)*du(x/i); 57 return ff[x]=ans; 58 } 59 60 il ll g(RG ll x,RG ll y){ 61 if (x<=1) return x; 62 if (!y) return du(x); 63 if (gg[x][y]) return gg[x][y]; 64 return gg[x][y]=g(x,y-1)+g(x/di[y],y); 65 } 66 67 il void work(){ 68 n=gi(),m=gi(),k=gi(); NN=min(n,m); 69 nn=min(NN,5000000LL); pre(); 70 for (RG ll d=1;d<=NN;d=pos+1){ 71 pos=min(n/(n/d),m/(m/d)); 72 ans+=(g(pos,kcnt)-g(d-1,kcnt))*(ll)(n/d)*(ll)calc(m/d); 73 } 74 printf("%lld\n",ans); return; 75 } 76 77 int main(){ 78 work(); 79 return 0; 80 }