• 数论题选做(一)


    不确定有没有(二)。


    POJ2773 Happy 2006

    方法一

    二分答案 (mid),然后容斥出 ([1,mid]) 内与 (m) 互质的个数,(O(2^7log K))

    方法二

    注意到答案就是缩系中的数加上 (km,forall kinmathbb{N})。用更损相减术证明平凡,(O(m))

    CF900D Unusual Sequences

    注意到 (sum a_i) 必是 (x) 的倍数,所以如果 (x mid y) 显然无解。否则另 (m=y/x),则题意转化为求 (gcdlimits_{i=1}^n a_i=1,sum a_i=m) 的序列个数。
    先不考虑 (gcd a_i=1) 的条件,答案是 (sum_{i=1}^m{m-1choose i-1}=2^{m-1})(注意这里是有序数列,刚开始看错了懵了好久),记这个答案为 (g(m))。再记考虑了 (gcd a_i=1) 的限制后序列之和等于 (m) 的答案为 (f(m))。注意到 (forall dmid m)(f(d)) 对应的序列同时乘 (m/d),就对应了 (g(m)) 中满足条件的序列,即

    [g(m)=sum_{dmid m}f(d) ]

    莫比乌斯反演得

    [f(m)=sum_{dmid m}mu(frac{m}{d})g(d) ]

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+5,P=1e9+7;
    int x,y,n,ans,pri[N],v[N];
    
    int qpow(int p_)
    {
        int r_=1,b_=2;
        for(;p_;p_>>=1,b_=1LL*b_*b_%P)
            if(p_&1) r_=1LL*r_*b_%P;
        return r_;
    }
    
    int mu(int x)
    {
        int res=1;
        for(int i=1;i<=pri[0]&&1LL*pri[i]*pri[i]<=x;++i)
        {
            int cnt=0;
            while(x%pri[i]==0) x/=pri[i],++cnt;
            if(cnt>1) return 0;
            else if(cnt==1) res*=-1;
        }
        if(x>1) res*=-1;
        return res==-1?P-1:res;
    }
    
    int main()
    {
        scanf("%d%d",&x,&y),n=y/x;
        if(y%x) return puts("0"),0;
        for(int i=2;i<=N-5;++i)
        {
            if(!v[i]) pri[++pri[0]]=i;
            for(int j=1;j<=pri[0]&&i*pri[j]<=N-5;++j)
            {
                v[i*pri[j]]=1;
                if(i%pri[j]==0) break;
            }
        }
        for(int i=1;i*i<=n;++i) if(n%i==0)
        {
            (ans+=1LL*qpow(i-1)*mu(n/i)%P)%=P;
            if(i*i!=n) (ans+=1LL*mu(i)*qpow(n/i-1)%P)%=P;
        }
        printf("%d",ans);
        return 0;
    }
    

    [HAOI2008]圆上的整点

    你想知道的都在这里。
    按照视频中的想法,答案就是所有形如 (4k+1) 的质因子的次数加一之积乘 (4)

    #include <bits/stdc++.h>
    using namespace std;
    
    int r; long long ans=1;
    
    int main()
    {
        scanf("%d",&r);
        for(int i=2;i*i<=r;++i) if(r%i==0)
        {
            int p=0;
            while(r%i==0) r/=i,++p;
            if(i%4==1) ans*=p<<1|1;
        }
        if(r>1&&r%4==1) ans*=3;
        printf("%lld",ans<<2);
        return 0;
    }
    

    CF1229B Kamil and Making a Stream

    有结论:所有不同的 (f(u,v)) 的值不超过 (O(log 10^{12})) 个,考虑一条路径上增加一个权值,如果此时 (gcd) 发生了变化,则必然是之前 (gcd) 的一个因子,这样最多不同的值只能有 (log) 个。我们可以每个点开个map储存每个值的出现次数,向儿子转移即可,复杂度 (O(nlog^2 10^{12}))

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int N=1e5+5,P=1e9+7;
    int n; ll w[N],ans;
    map<ll,int> p[N];
    vector<int> G[N];
    
    void dfs(int u,int fa)
    {
        ++p[u][w[u]];
        for(auto &it:p[u]) (ans+=it.first*it.second%P)%=P;
        for(int v:G[u]) if(v!=fa)
        {
            for(auto &it:p[u])
                p[v][__gcd(it.first,w[v])]+=it.second;
            dfs(v,u);
        }
    }
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%lld",w+i);
        for(int i=1,a,b;i<n;++i)
        {
            scanf("%d%d",&a,&b);
            G[a].push_back(b),G[b].push_back(a);
        }
        dfs(1,0),printf("%lld",ans);
        return 0;
    }
    

    CF1166E The LCMs Must be Large

    有结论:能构造出符合限制的序列当且仅当所有集合两两有交。
    反证法。设 (f(S)) 是集合 (S) 中所有位置数的最小公倍数。假设存在一个满足条件的序列,且有两个不交的集合 (A,B),那么有 (f(A)> f(U-A)ge f(B)>f(U-B)ge f(A)),上式中 (f(U-A)ge f(B)) 是因为如果 (S_1subset S_2),则显然 (f(S_1)le f(S_2))。上式推出了 (f(A)>f(A)) 的荒谬结果,很明显出现了矛盾,原命题得证。
    考虑在满足条件的情况下如何构造这个序列,取 (m) 个互不相同的质数 (p_1,p_2,dots,p_m),令每个位置 (a_i=prod_{iin S_k}p_k)。注意到每个集合的 lcm 必是 (prod_{i=1}^mp_i),而这个集合的补集必没有 (p_i) 这个数,所以构造成立。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e4+5;
    int n,m;
    bitset<N> s[N];
    
    int main()
    {
        scanf("%d%d",&m,&n);
        for(int i=1,k,x;i<=m;++i)
        {
            scanf("%d",&k);
            while(k--) scanf("%d",&x),s[i][x]=1;
        }
        for(int i=1;i<=m;++i)
            for(int j=i+1;j<=m;++j)
                if((s[i]&s[j]).none()) return puts("impossible"),0;
        puts("possible");
        return 0;
    }
    

    [NOI2010]能量采集

    答案即是

    [2sum_{i=1}^nsum_{j=1}^mgcd(i,j)-nm ]

    考虑 (sum_{i=1}^nsum_{j=1}^mgcd(i,j)) 如何求,考虑容斥。
    (g(x)) 等于公因数(不是最大)为 (x) 的点对个数,(f(x)) 则是最大公因数。

    [f(x)=g(x)-sum_{i=1}^{frac{n}{i}}f(ix) ]

    从后向前递推,(O(nlog n))

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+5;
    int n,m; long long f[N],ans;
    
    int main()
    {
        scanf("%d%d",&n,&m);
        if(n>m) swap(n,m);
        for(int i=n;i;--i)
        {
            f[i]=1LL*(n/i)*(m/i);
            for(int j=i*2;j<=n;j+=i) f[i]-=f[j];
            ans+=(2*i-1)*f[i];
        }
        printf("%lld",ans);
        return 0;
    }
    

    [CQOI2015]选数

    和上一道题一样,考虑先 (Lgets L/K,Rgets R/K),那么现在变成在 ([L,R]) 中选择 (n) 个最大公约数为 (1) 的方法。设 (f(i)) 为选出的最大公约数为 (i) 且不全相同的方案数,则有

    [f(i)=x^n-x-sum_{idle R-L} f(id) ]

    其中 (x) 是区间 ([L,R])(i) 的倍数个数。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+5,P=1e9+7;
    int f[N],n,k,l,r;
    
    int qpow(int b_,int p_)
    {
        int r_=1;
        for(;p_;p_>>=1,b_=1LL*b_*b_%P)
            if(p_&1) r_=1LL*r_*b_%P;
        return r_;
    }
    
    int main()
    {
        scanf("%d%d%d%d",&n,&k,&l,&r),l=l/k+(l%k>0),r/=k;
        if(l>r) return puts("0"),0;
        for(int i=r-l;i;--i)
        {
            int L=l/i+(l%i>0),R=r/i;
            if(L>R) continue;
            f[i]=(qpow(R-L+1,n)-(R-L+1)+P)%P;
            for(int j=i*2;j<=r-l;j+=i) f[i]=(f[i]-f[j]+P)%P;
        }
        printf("%d",(f[1]+(l==1))%P);
        return 0;
    }
    

    [SCOI2010]幸运数字

    先预处理出幸运数字,然后爆搜容斥,当然不优化显然会 T。
    考虑降序排序,并且提前预处理出互为因子的数,只留一种。容斥时如果超过边界及时剪纸(注意中间过程有可能爆long long)。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int N=4030;
    ll l,r,a[N],num[N],ans;
    int n,tot,vis[N];
    
    void getnum(ll now)
    {
        if(now>r) return;
        if(now) a[++n]=now;
        getnum(now*10+6),getnum(now*10+8);
    }
    
    void dfs(int id,int sgn,ll val)
    {
        if(val>r) return;
        if(id>tot)
        {
            if(!sgn) return;
            ans+=(r/val-l/val-(l%val>0)+1)*((sgn&1)?1:-1);
            return;
        }
        dfs(id+1,sgn,val); val=val/__gcd(val,num[id]);
        if(1.0*val*num[id]<=r) dfs(id+1,sgn+1,val*num[id]);
    }
    
    int main()
    {
        scanf("%lld%lld",&l,&r);
        getnum(0),sort(a+1,a+n+1);
        for(int i=1;i<=n;++i)
        {
            if(!vis[i]) num[++tot]=a[i];
            for(int j=i+1;j<=n;++j)
                if(a[j]%a[i]==0) vis[j]=1;
        }
        reverse(num+1,num+tot+1);
        dfs(1,0,1),printf("%lld",ans);
        return 0;
    }
    

    SGU499 Greatest Greatest Common Divisor

    sb 题,塞桶里,然后倒着枚举值域就行了。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e6+5;
    int n,a[N];
    
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
            memset(a,0,sizeof(a));
            for(int i=1,x;i<=n;++i)
                scanf("%d",&x),++a[x];
            for(int i=N-5;i;--i)
            {
                int cnt=0;
                if(a[i]) ++cnt;
                for(int j=2*i;j<=N-5;j+=i) cnt+=a[j];
                if(cnt>1) {printf("%d
    ",i);break;}
            }
        }
        return 0;
    }
    

    HDOJ4135 Co-prime

    身败名裂.jpg
    又是一道裸容斥,不放代码了。

    CF1176D Recover it!

    把序列按照质数与合数分开,合数降序排序,质数升序排序。遍历合数时,如果出现次数为零则为答案,否则将出现次数减一,将最大真因子加一,质数同理。容易发现这样子一定是对的。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=2e5+5,M=2750131+5;
    int n,pri[N],p[N*2],d[N*2],t1,t2,mind[M];
    bool v[M]; map<int,int> mp;
    
    int main()
    {
        for(int i=2;i<=M-5;++i)
        {
            if(!v[i]) pri[++pri[0]]=i;
            for(int j=1;j<=pri[0]&&i*pri[j]<=M-5;++j)
            {
                v[i*pri[j]]=1,mind[i*pri[j]]=pri[j];
                if(i%pri[j]==0) break;
            }
        }
        scanf("%d",&n);
        for(int i=1,x;i<=n*2;++i)
        {
            scanf("%d",&x);
            if(v[x]) d[++t2]=x;
            else p[++t1]=x;
        }
        sort(p+1,p+t1+1),sort(d+1,d+t2+1,greater<int>());
        for(int i=1;i<=t2;++i)
        {
            if(mp[d[i]]) {--mp[d[i]];continue;}
            printf("%d ",d[i]);
            ++mp[d[i]/mind[d[i]]];
        }
        for(int i=1;i<=t1;++i)
        {
            if(mp[p[i]]) {--mp[p[i]];continue;}
            printf("%d ",p[i]),++mp[pri[p[i]]];
        }
        return 0;
    }
    

    CF1174E Ehab and the Expected GCD Problem

    很牛逼的一道题。参考题解
    我们想让不同 (gcd) 个数尽量多,那么首先要让第一个数的 (gcd) 尽量大,每次要减的尽量少。那么第一位只能是 (2^x3^y) 形式((yin{0,1}))。
    (f_{i,x,y}) 表示第 (i) 位形如 (2^x3^y) 的序列个数,转移详见代码。

    #include <bits/stdc++.h>
    #define c(x) (n/(x))
    using namespace std;
    
    const int N=1e6+5,P=1e9+7;
    int n,k,f[N][21][2];
    
    int main()
    {
        scanf("%d",&n),f[1][k=log2(n)][0]=1;
        if((1<<k-1)*3<=n) f[1][k-1][1]=1;
        for(int i=2;i<=n;++i)
            for(int j=0;j<=k;++j)
                f[i][j][0]=(1LL*f[i-1][j][0]*(c(1<<j)-i+1)+
                1LL*f[i-1][j+1][0]*(c(1<<j)-c(1<<j+1))+
                1LL*f[i-1][j][1]*(c(1<<j)-c((1<<j)*3)))%P,
                f[i][j][1]=(1LL*f[i-1][j][1]*(c((1<<j)*3)-i+1)+
                1LL*f[i-1][j+1][1]*(c((1<<j)*3)-c((1<<j+1)*3)))%P;
        printf("%d",f[n][0][0]);
        return 0;
    }
    

    HDOJ5514 Frogs

    一个青蛙能跳到的点为 (gcd(a_i,m)),那么我们考虑只在 (m) 的因子处统计这些值,将因子提出来后答案就是与这个值互素的值之和,也就是 (sumlimits_{dmid m}varphi(m/d)m/2)

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e4+5;
    int n,m,a[N],fl,fac[N],tot;
    long long ans;
    
    int phi(int x)
    {
        int res=x; 
        for(int i=2;i*i<=x;++i) if(x%i==0)
        {
            while(x%i==0) x/=i;
            res=res/i*(i-1);
        }
        if(x>1) res=res/x*(x-1);
        return res;
    }
    
    int main()
    {
        int Case; scanf("%d",&Case);
        for(int T=1;T<=Case;++T)
        {
            scanf("%d%d",&n,&m),fl=ans=tot=0;
            for(int i=1;i<=n;++i)
                scanf("%d",a+i),a[i]=__gcd(m,a[i]),fl|=(a[i]==1);
            printf("Case #%d: ",T);
            if(fl) {printf("%lld
    ",1LL*(m-1)*m/2); continue;}
            for(int i=2;i*i<=m;++i) if(m%i==0)
            {
                fac[++tot]=i;
                if(i*i!=m) fac[++tot]=m/i;
            }
            for(int i=1;i<=tot;++i)
                for(int j=1;j<=n;++j) if(fac[i]%a[j]==0)
                    {ans+=1LL*phi(m/fac[i])*m/2; break;}
            printf("%lld
    ",ans);
        }
        return 0;
    }
    
  • 相关阅读:
    java泛型详解
    一条sql语句搞定基于mysql的sql执行顺序的基本理解
    java深拷贝和浅拷贝
    Intelligent idea高效实用总结
    git工具的使用总结
    java注解详解
    实现业务系统中的用户权限管理设计篇
    有创业兴趣的来看看
    System.Web.HttpRequestValidationException:解决方法
    XMLHttpRequest 对象
  • 原文地址:https://www.cnblogs.com/wzzyr24/p/13553902.html
Copyright © 2020-2023  润新知