bzoj4652 [Noi2016]循环之美(Mobius反演+杜教筛+Hash表)

34 篇文章 0 订阅

挖了很久的坑qaq
还记得第一次xtx神犇给我们讲这道题,已经过去快整整一年了呢owo

首先怎么算是纯循环小数呢?
因为要求数值不同的数对个数,因此我们只考虑gcd(x,y)==1的数对(x,y)。
什么时候会出现循环呢?当出现相同余数时。那纯循环呢?就要求第一次出现的相同余数为x%y。即只需要满足 xklxmody,l>0 x k l ≡ x mod y , l > 0 ,其中l就是循环节长度。
又因为(x,y)=1,所以 (k,y)=1 ( k , y ) = 1
因此我们就是要求 x=1ny=1m[gcd(x,y)==1,gcd(y,k)==1] ∑ x = 1 n ∑ y = 1 m [ gcd ( x , y ) == 1 , gcd ( y , k ) == 1 ]
y=1m[gcd(y,k)==1]x=1n[gcd(x,y)==1] ∑ y = 1 m [ gcd ( y , k ) == 1 ] ∑ x = 1 n [ gcd ( x , y ) == 1 ]
把后一部分Mobius反演一下就是
y=1m[gcd(y,k)==1]d|ynμ(d)nd ∑ y = 1 m [ gcd ( y , k ) == 1 ] ∑ d | y n μ ( d ) ⌊ n d ⌋
改变枚举顺序可以得到:
d=1nμ(d)ndy=1md[gcd(yd,k)==1] ∑ d = 1 n μ ( d ) ⌊ n d ⌋ ∑ y = 1 ⌊ m d ⌋ [ g c d ( y d , k ) == 1 ]
d=1n[gcd(d,k)==1]μ(d)ndy=1md[gcd(y,k)==1] ∑ d = 1 n [ gcd ( d , k ) == 1 ] μ ( d ) ⌊ n d ⌋ ∑ y = 1 ⌊ m d ⌋ [ gcd ( y , k ) == 1 ]
f(n,k)=i=1n[gcd(i,k)==1] f ( n , k ) = ∑ i = 1 n [ g c d ( i , k ) == 1 ]
我们显然有 f(n,k)=nkf(k,k)+f(n%k,k) f ( n , k ) = ⌊ n k ⌋ f ( k , k ) + f ( n % k , k )
因此我们可以预处理出 O(K) O ( K ) 级别的f的前缀和,这样就可以 O(1) O ( 1 ) 计算后一部分了,这样总体复杂度就是 O(n) O ( n ) ,可以获得84分。

考虑进一步的优化,记 g(n,k)=i=1n[gcd(i,k)==1]μ(i) g ( n , k ) = ∑ i = 1 n [ g c d ( i , k ) == 1 ] μ ( i ) 如果我们可以处理出这个东西得前缀和我们就可以分块算了。

我们先考虑 k k 的一个质因数p,那么 k k 显然可以写成pcq的形式。由于在[1,n]的范围中只有与 k k 互质的才是有效值,那么若(x,k)==1,我们可以得到(x,p)==1且(x,q)==1。于是,我们可以考虑从(x,q)==1的取值中减去x不与p互质的部分,就可以得到(x,k)==1的部分。
剩下的部分去看大神blog吧,懒得搬了qaq
portal
最后g这个函数是可以递归计算的。复杂度O(logkw(k)),w(k)<=4
边界为k=1时函数值就是mu的前缀和,可以杜教筛来搞,n<=1时为n。
总的复杂度大概是 O(nw(k)+n2/3) O ( n w ( k ) + n 2 / 3 )

要写hash表qaq然而我还是跑不过luogu,人称被卡常数。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 1000010
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int prime[N>>3],tot=0,a[10],cnt=0,K,h[N],num=0,h1[5][N],num1=0;
ll mu[N],f[2010],ans=0;
bool notprime[N];
struct Hash_Table{
    int key,val,next;
}data[N],data1[N<<3];
inline int hs(int key){
    int x=key%N;
    for(int i=h[x];i;i=data[i].next)
        if(data[i].key==key) return data[i].val;
    return -1;
}
inline int ins(int key,int val){
    int x=key%N;
    data[++num].next=h[x];h[x]=num;data[num].key=key;data[num].val=val;
}
inline int gcd(int x,int y){return y?gcd(y,x%y):x;}
inline void init(){
    notprime[1]=1;mu[1]=1;
    for(int i=2;i<=1e6;++i){
        if(!notprime[i]) prime[++tot]=i,mu[i]=-1;
        for(int j=1;prime[j]*i<=1e6;++j){
            notprime[prime[j]*i]=1;
            if(i%prime[j]==0){
                mu[i*prime[j]]=0;break;
            }mu[i*prime[j]]=-mu[i];
        }
    }for(int i=2;i<=1e6;++i) mu[i]+=mu[i-1];
    for(int i=1;i<=K;++i) if(gcd(K,i)==1) f[i]=1;
    for(int i=1;i<=K;++i) f[i]+=f[i-1];
    for(int i=1;prime[i]<=K;++i) if(K%prime[i]==0) a[++cnt]=prime[i];
}
inline ll calm(int n){return (n/K)*f[K]+f[n%K];}
inline ll calmu(int n){
    if(n<=1e6) return mu[n];
    int res=hs(n);if(res>0) return res;res=1;
    for(int i=2,lst;i<=n;i=lst+1){
        lst=n/(n/i);res-=calmu(n/i)*(lst-i+1);
    }ins(n,res);return res;
}
inline ll g(int n,int k){
    if(!k) return calmu(n);
    if(n<=1) return n;int x=n%N;
    for(int i=h1[k][x];i;i=data1[i].next)
        if(data1[i].key==n) return data1[i].val;
    ll res=g(n,k-1)+g(n/a[k],k);
    data1[++num1].next=h1[k][x];h1[k][x]=num1;data1[num1].key=n;
    data1[num1].val=res;return res;
}
int main(){
//  freopen("a.in","r",stdin);
    int n=read(),m=read();K=read();init();
    for(int i=1,lst;i<=min(n,m);i=lst+1){
        lst=min(n/(n/i),m/(m/i));
        ans+=(g(lst,cnt)-g(i-1,cnt))*(n/i)*calm(m/i);
    }printf("%lld\n",ans);
    return 0;
}

7.3upd:重打了一遍就跑过去了???还贼快???

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 1000003
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int K,mu[N],prime[N>>3],tot=0,f[2010],h1[5][N],num1=0,cnt=0,a[5],num=0,h[N];
bool notprime[N];
struct Hash_table{
    int key,val,next;
}data1[N],data[N];
inline int gcd(int x,int y){return y?gcd(y,x%y):x;}
inline void init(){
    notprime[1]=1;mu[1]=1;
    for(int i=2;i<=N-3;++i){
        if(!notprime[i]) prime[++tot]=i,mu[i]=-1;
        for(int j=1;prime[j]*i<=N-3;++j){
            notprime[prime[j]*i]=1;
            if(i%prime[j]==0){
                mu[i*prime[j]]=0;break;
            }mu[i*prime[j]]=-mu[i];
        }
    }for(int i=1;i<=N-3;++i) mu[i]+=mu[i-1];
    for(int i=1;i<=K;++i) f[i]=gcd(i,K)==1;
    for(int i=1;i<=K;++i) f[i]+=f[i-1];
    for(int i=1;prime[i]<=K;++i) if(K%prime[i]==0) a[++cnt]=prime[i];
}
inline ll F(int x){return x/K*f[K]+f[x%K];}
inline int calmu(int n){
    if(n<=1e6) return mu[n];
    int x=n%N;
    for(int i=h[x];i;i=data[i].next)
        if(data[i].key==n) return data[i].val;
    int res=1;
    for(int i=2,lst;i<=n;i=lst+1){
        lst=n/(n/i);res-=calmu(n/i)*(lst-i+1);
    }data[++num].next=h[x];h[x]=num;data[num].key=n;data[num].val=res;
    return res;
}
inline ll g(int n,int k){
    if(!k) return calmu(n);if(n<=1) return n;
    int x=n%N;
    for(int i=h1[k][x];i;i=data1[i].next)
        if(data1[i].key==n) return data1[i].val;
    int res=g(n,k-1)+g(n/a[k],k);
    data1[++num1].next=h1[k][x];h1[k][x]=num1;data1[num1].key=n;
    data1[num1].val=res;return res;
}
int main(){
//  freopen("a.in","r",stdin);
    int n=read(),m=read();K=read();init();ll ans=0;
    for(int i=1,lst;i<=min(n,m);i=lst+1){
        lst=min(n/(n/i),m/(m/i));
        ans+=(g(lst,cnt)-g(i-1,cnt))*(n/i)*F(m/i);
    }printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值