蒟蒻不想取名字

这个博客可以拿来放模板诶~

bzoj4652

这道题感觉非常优美……能有一个这么优美的题面和较高的思维难度真的不容易……

  为了表示方便,让我先讲一下两个符号。[a][a]表示如果aa为真,那么返回11,否则返回00; a⊥ba⊥b表示aa与bb互质。

  首先,我们需要考虑一个分数要成为纯循环小数需要满足什么条件。

  我们先来回想一下,我们是怎样使用除法来判断一个分数xyxy是否是纯循环小数的。显然我们是一路除下去,什么时候出现了相同的余数,那么这个数就是一个循环小数。如果第一个重复的余数是xx,那么这个数就是纯循环小数。这种方法实际上就是存在一个数l≠0l≠0,使得:

xkl≡x(mody)xkl≡x(mody)

  又由于题目要求值不重复,那么我们可以得到x⊥yx⊥y。所以,我们可以推出kl≡1(mody)kl≡1(mody)。所以我们只需k⊥yk⊥y即可。

  于是,我们实际上是要求这个式子:

=∑x=1n∑y=1m[x⊥y][y⊥k]∑y=1m[y⊥k]∑x=1n[x⊥y]∑x=1n∑y=1m[x⊥y][y⊥k]=∑y=1m[y⊥k]∑x=1n[x⊥y]

  用一下莫比乌斯反演,可以得到:

=∑y=1m[y⊥k]∑x=1n∑d|x,d|yμ(d)∑y=1m[y⊥k]∑d|ynμ(d)⌊nd⌋∑y=1m[y⊥k]∑x=1n∑d|x,d|yμ(d)=∑y=1m[y⊥k]∑d|ynμ(d)⌊nd⌋

  再通过换一下枚举顺序,可得:

==∑d=1nμ(d)⌊nd⌋∑y=1m[d | y][y⊥k]∑d=1nμ(d)⌊nd⌋∑i=1⌊md⌋[id⊥k]∑d=1n[d⊥k]μ(d)⌊nd⌋∑i=1⌊md⌋[i⊥k]∑d=1nμ(d)⌊nd⌋∑y=1m[d | y][y⊥k]=∑d=1nμ(d)⌊nd⌋∑i=1⌊md⌋[id⊥k]=∑d=1n[d⊥k]μ(d)⌊nd⌋∑i=1⌊md⌋[i⊥k]

  最后一步用了互质的一个性质,那就是[ab⊥c]=[a⊥c][b⊥c][ab⊥c]=[a⊥c][b⊥c]。这点应该很好理解吧。

  我们不妨设f(n,k)=∑ni=1[i⊥k]f(n,k)=∑i=1n[i⊥k],那么我们只要在O(1)O(1)的时间内求出ff,复杂度就是O(n)O(n),就可以得到8484分。实际上,我们可以得到一个式子:

f(n,k)=⌊nk⌋f(k,k)+f(nmodk,k)f(n,k)=⌊nk⌋f(k,k)+f(nmodk,k)

  这个应该也不难理解,因为[a⊥b]=[amodb⊥b][a⊥b]=[amodb⊥b]

  于是我们就可以对f(n,k)f(n,k)预处理出n≤kn≤k的值,每次求值就变成O(1)O(1)了。于是8484分到手了。

  我们考虑接下来该如何优化。由于⌊mx⌋⌊mx⌋只有m−−√m种取值,⌊nx⌋⌊nx⌋只有n√n种取值,于是我们显然可以分段求和。然后,我们就需要快速求出∑ni=1[i⊥k]μ(i)∑i=1n[i⊥k]μ(i)的值。

  不妨设g(n,k)=∑ni=1[i⊥k]μ(i)g(n,k)=∑i=1n[i⊥k]μ(i),我们来考虑一下这个函数如何快速求。我们先考虑kk的一个质因数pp,那么kk显然可以写成pcqpcq的形式。由于在[1,n][1,n]的范围中只有与kk互质的才是有效值,那么若x⊥kx⊥k,我们可以得到x⊥px⊥p并且x⊥qx⊥q。于是,我们可以考虑从x⊥qx⊥q的取值中减去xx不与pp互质的部分,就可以得到x⊥kx⊥k的部分。这里如果不懂的话可以自己画一个x⊥qx⊥q,x⊥px⊥p,x⊥kx⊥k的关系图理解一下。

  由于所有与qq互质的数一定可以写成pxy(y⊥q)pxy(y⊥q)的形式。那么我们需要减去的数一定满足x>0x>0。又由于当x>1x>1时μ(pxy)=0μ(pxy)=0,所以我们只需要考虑x=1x=1的情况即可。在这种情况下,我们需要考虑的数就是py(y⊥q)py(y⊥q)的形式,所以我们可以得到如下式子:

g(n,k)=∑i=1n[i⊥q]μ(i)−∑y=1⌊np⌋[py⊥q]μ(py)=g(n,q)−∑y=1⌊np⌋[y⊥q]μ(py)g(n,k)=∑i=1n[i⊥q]μ(i)−∑y=1⌊np⌋[py⊥q]μ(py)=g(n,q)−∑y=1⌊np⌋[y⊥q]μ(py)

  上面的最后一步是由于p⊥qp⊥q,所以py⊥qpy⊥q只需在保证y⊥qy⊥q即可。

  我们接着来考虑后一个式子。显然当p⊥yp⊥y的时候μ(py)=μ(p)μ(y)μ(py)=μ(p)μ(y),否则μ(py)=0μ(py)=0。于是,我们又得到了:

g(n,k)=g(n,q)−∑y=1⌊np⌋[y⊥p][y⊥q]μ(p)μ(y)=g(n,q)−μ(p)∑y=1⌊np⌋[y⊥k]μ(y)=g(n,q)+g(⌊np⌋,k)g(n,k)=g(n,q)−∑y=1⌊np⌋[y⊥p][y⊥q]μ(p)μ(y)=g(n,q)−μ(p)∑y=1⌊np⌋[y⊥k]μ(y)=g(n,q)+g(⌊np⌋,k)

  于是我们就可以递归求解了。容易发现边界情况就是n=0n=0或者k=1k=1。n=0n=0的时候直接返回00就可以了,k=1k=1的时候就是莫比乌斯函数的前缀和,用杜教筛求出来就可以了。由于每次递归要么nn会变成⌊np⌋⌊np⌋,有O(n√)O(n)种取值;要么pp少了一个质因数,有ω(k)ω(k)种取值,所以总共有O(ω(k)n√)O(ω(k)n)种取值,记忆化搜索即可。其中ω(k)ω(k)表示kk的不同的质因子数目。于是最后的总复杂度为O(ω(k)n√+n23)O(ω(k)n+n23),可以通过此题。

  两个细节:

  可以在递归求gg的时候不直接记kk,而是记录kk还剩下多少个质因数,方便存储;

  记忆化的时候可以开哈希链表存储,用mapmap也可以,或者干脆分n,mn,m以及小于、大于根号的情况存储也可以。

  第一次写这种题的题解,好像写得过于详细了(其实是因为我什么都不会),请大神们不要喷……

  下面贴代码:

#include<cstdio>

#include<algorithm>

#include<cstring>

#include<map>

using namespace std;

#define long long long

#define to i*prime[j]

#define maxn 1000005

long n,m,k;

int p;

int miu[maxn];

int prime[maxn];

int sum[maxn];

int k_prf[10];

bool sift[maxn];

map<int,int> mp;

map<int,int> ms[10];

map<int,int> mt[10];

void init()

{

miu[1]=sum[1]=1;

for(int i=2;i<maxn;i++)

{

if(!sift[i])

{

prime[++p]=i;

miu[i]=-1;

}

sum[i]=sum[i-1]+miu[i];

for(int j=1;j<=p&&to<maxn;j++)

{

sift[to]=true;

if(i%prime[j]==0) 

{

miu[to]=0;

break;

}

else miu[to]=-miu[i];

}

}

int sum_miu(int x)

{

if(x<maxn) return sum[x];

if(mp.count(x)) return mp[x];

int ans=1;

for(int i=2,p;p<x;i=p+1)

{

p=x/(x/i);

ans-=sum_miu(x/i)*(p-i+1);

}

return mp[x]=ans;

}

long S(int x,int r)//sum_miu of 1~r coprime with the first x-th prmfactors of k

{

if(!x) return sum_miu(r);

if(r<=1) return r;

if(ms[x].count(r)) return ms[x][r];

return ms[x][r]=S(x-1,r)+S(x,r/k_prf[x]);

}

int T(int x,int r)//numbers of 1~r ...

{

if(!x) return r;

if(r<=1) return r;

if(mt[x].count(r)) return mt[x][r];

return mt[x][r]=T(x-1,r)-T(x-1,r/k_prf[x]);

}

int main()

{

init();

scanf("%lld%lld%lld",&n,&m,&k);

int kk=k;

p=0;

for(int i=1;kk>1;i++)

 {

if(kk%prime[i]==0) 

 {

k_prf[++p]=prime[i];

while(kk%prime[i]==0) 

kk/=prime[i];

}

 }

long ans=0ll;

for(int i=1,t;i<=n&&i<=m;i=t+1)

 {

t=min(m/(m/i),n/(n/i));

ans+=(n/i)*(S(p,t)-S(p,i-1))*T(p,m/i);

 }

printf("%lld",ans);

}


评论