数论题选做(一)

不确定有没有(二)。


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;
}
原文地址:https://www.cnblogs.com/wzzyr24/p/13553902.html