[Kuangbin带你飞]专题十四 数论(一)

Bi-shoe and phi-shoe  知识点:欧拉函数打表

题意:竹子的得分为它长度的欧拉函数值,Bi-shoe想买竹子给同学,每个同学收到的竹子得分>=他的幸运数字,竹子每单位长度需要花1Xukha。问Bi-shoe最少花多少钱?

思路:欧拉值打表,遍历

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e4+10;
const int M=1e6+10;
int euler[M];
int a[N];
void gete()
{
    for(int i=1;i<=M;++i)
        euler[i]=i;
    for(int i=2;i<=M;++i)
    {
        if(euler[i]==i)   //说明是素数
            for(int j=i;j<=M;j+=i) //素数的倍数的euler[j]一并改掉
                euler[j]=euler[j]/i*(i-1);          //euler[j]=euler[j]*(1-1/i)
    }
    /*for(int i=2;i<=10;++i)
        cout<<i<<' '<<euler[i]<<endl;*/
}
int main()
{
    gete();
    int T;
    cin>>T;
    for(int k=1;k<=T;k++)
    {
        int n;
        cin>>n;
        long long ans=0;
        for(int i=1;i<=n;++i)
            cin>>a[i];
        sort(a+1,a+1+n);
        for(int i=1,j=2;i<=n&&j<=M;j++)
        {
            if(euler[j]>=a[i])
            {
                ans+=j;
                i++;
                j--;
            }
        }
        cout<<"Case "<<k<<": "<<ans<<' '<<"Xukha"<<endl;
    }
    return 0;
}

Sigma Function  知识点:规律

记得当时看了很多推导,后悔当时没有写博客的习惯。

题意:1~n中有多少个数,其因子和为偶数

规律:因子和为奇数的有:平方数以及平方数的二倍。总数减去为奇数的就是偶数的

智商不够就暴力打表找规律叭_(:з」∠)_

#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;
typedef long long LL;
int main()
{
    int T;
    cin>>T;
   for(int i=1;i<=T;++i)
   {
        LL n;
        cin>>n;
        LL ans=(LL)sqrt(n)+(LL)sqrt(n/2);  //中间不强制转换的话会wa
        printf("Case %d: %lld\n",i,n-ans);
   }
       return 0;
}

Leading and Trailing  知识点:快速模指数算法  指数问题转成对数求解

题意:求n^k的前三位和后三位

思路:后三位很好求,只要模1000就能求出,关键是前三位

前三位的求法是这样的,将n^k用科学计数法表示,那么n^k=a\times 10^x  n^k前三位即a\times 100取整

我们将a用10^y表示,由于科学计数法中a<10,所以y一定是小数,即n^k=10^{x+y} 

y=k1og(n)-x 

//A
#include<stdio.h>
#include<iostream>
#include<math.h>
using namespace std;
typedef long long ll;
const int mod=1000;
ll quick_mod(ll a,ll k)
{
    if(k==0) return 1;
    ll ans=1;
    while(k)
    {
        if(k&1)
            ans=ans*a%mod;
        a=a*a%mod;
        k=k>>1;
    }
    return ans;
}
int main()
{
    int T;
    cin>>T;
    for(int kase=1;kase<=T;kase++)
    {

        ll n,k;
        cin>>n>>k;
        ll trail=quick_mod(n,k);  //只要模1000就能算出最后三位
       double x=k*log10(n)-(int)(k*log10(n));   //强制转换要加括号
       double tmp=pow(10,x);
       int lead=(int)(tmp*100);
        printf("Case %d: %d %03d\n",kase,lead,trail);
    }
    return 0;
}

  Goldbach`s Conjecture  知识点:素数打表

题意:给一个数n,问能找到多少素数对,其和是n

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int N=1e7+10;
bool tag[N];
int prime[1000000];
int cnt=0;
void getprime()
{

    memset(tag,false,sizeof(tag));
    tag[0]=tag[1]=1;
    for(int i=2;i<=N;++i)
    {
        if(!tag[i])
            prime[++cnt]=i;
        for(int j=1;j<=cnt&&prime[j]*i<=N;++j)
        {
            tag[prime[j]*i]=true;
            if(i%prime[j]==0)
                break;
        }

    }
}
int main()
{
    getprime();
    int T;
    cin>>T;
    for(int k=1;k<=T;++k)
    {
        int n;
        cin>>n;
        int ans=0;
        //cout<<"keke"<<endl;
        for(int i=1;i<=cnt&&2*prime[i]<=n;++i)
        {
           // cout<<"keke"<<tag[n-prime[i]]<<endl;
            if(tag[n-prime[i]]==false)
                ans++;
        }
        printf("Case %d: %d\n",k,ans);
    }
    return 0;
}

G - Harmonic Number (II)   找规律

用正确方法求h(n)

#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long LL;
LL h(LL n)
{
    LL res=0;
    LL i=1;
   for(;i*(i+1)<n;++i)
   {
       LL num=n/i-n/(i+1);
       //cout<<i<<":"<<num<<endl;
       res+=num*i;
   }
//   cout<<i<<endl;
//   cout<<res<<endl;
    LL k=n/i;
    for(LL j=1;j<=k;++j)
        res+=n/j;

    return res;
}
int main()
{
   // freopen("in.txt","r",stdin);
    int T;
    cin>>T;
    for(int kase=1;kase<=T;kase++)
    {
        LL n;
        cin>>n;
        cout<<"Case "<<kase<<": "<<h(n)<<endl;
    }
}

 

I - Harmonic Number

题意:求1 + 1/2 + 1/3 + 1/4 + 1/ 5 +...+ 1/ n

思路:有公式,记不住咋办?将数分组存储的思路

#include<iostream>
#include<stdio.h>
using namespace std;
const int N=1e8+10;
const int NN=1e8/40+10;
//每隔四十记录一次答案  不然空间不够,这个样子的话循环一次 1s 之后每个数进来最多循环39次
double a[NN];
void pre()
{
    double sum=0;
    for(int i=1;i<1e8+10;++i)
    {
        sum+=(1.0)/i;
        if(i%40==0) a[i/40]=sum;
    }
}
int main()
{
    //cout<<NN<<endl;
    pre();
    int T;
    cin>>T;
    int cas=0;
    while(T--)
    {
        ++cas;
        int n;
        cin>>n;
        int x=n/40;                     //将数锁定在一个范围,机智
        double ans=a[x];
        for(int i=x*40+1;i<=n;++i)
            ans+=1.0/i;
         printf("Case %d: %.10f\n",cas,ans);
    }

    return 0;
}

J - Mysterious Bacteria

题意:x=b^p 给出x,求最大的p

x=p_1^{\alpha_1}p_2^{\alpha_2}...p_n^{\alpha_n} =(p_1^{\alpha_1/y}p_2^{\alpha_2/y}...p_n^{\alpha_n/y})^y  这个y的最大值就是答案

显然y=gcd(\alpha_1,\alpha_2...\alpha_n)的时候最大

这题的坑点在于x可能是负的,我们在处理的时候把它转成正数去做的,如果y是偶数就会导致最终解为正数,所以要把一直除二,除到奇数为止

#include<iostream>
#include<math.h>
#include<string.h>
using namespace std;
typedef long long LL;
const int N=1e6+100;
bool tag[N];
int prime[N];
int cnt;
void getprime()
{
    memset(tag,0,sizeof(tag));
    tag[0]=tag[1]=1;
    for(int i=2;i<N;++i)
    {
        if(!tag[i])
            prime[++cnt]=i;
        for(int j=1;j<=cnt&&prime[j]*i<N;++j)
        {
            tag[prime[j]*i]=1;
            if(i%prime[j]==0)
                break;
        }
    }
//    for(int i=1;i<=100;++i)
//        cout<<prime[i]<<endl;
   // cout<<cnt<<" "<<prime[cnt]<<endl;   78504 1000099

}
int gcd(int a,int b)
{
    return a%b==0?b:gcd(b,a%b);
}
int solve(LL n)
{
    int flag=0;
    if(n<0)
    {

        n=-n;
        flag=1;
    }
    int ans=0;
    int i;
    for(i=1;i<=cnt&&prime[i]*prime[i]<=n;++i)
    {
        if(n%prime[i]==0)
        {
            while(n%prime[i]==0)
            {
                ans++;
                n/=prime[i];
            }
            break;
        }
    }//得到第一个整除的素因子
    if(ans==0) return 1;        //这句话不能少
    else
    {
        ++i;
        for(;i<=cnt&&prime[i]*prime[i]<=n;++i)
        {
            int e=0;
            if(n%prime[i]==0)
            {
                while(n%prime[i]==0)
                {
                    e++;
                    n/=prime[i];
                }
                ans=gcd(ans,e);//我他妈的把这句放在外面了,导致如果n%prime[i]!=0,ans=gcd(ans,0)
            }
        }
        if(flag)  //n是负数
        {
                while(ans%2==0)
                ans=ans>>1;
        }
    }
    return ans;

}
int main()
{
    getprime();
    int T;
    cin>>T;
    for(int kase=1;kase<=T;++kase)
    {
        LL x;   //不用LL的话会爆int有符号最大(2^16)
        cin>>x;
        cout<<"Case "<<kase<<": "<<solve(x)<<endl;
    }

    return 0;
}
//1073741824=2^30
//re 当9223372036854775807时 returned 0xC0000094  6位正数/负数时   最好的debug方式是先拿一个简单的不符合要求的数跑一下

K - Large Division

题意:b是32位有符号整数,判读a是否能整除b

数论的魅力在于:本来以为这道题必须要用大数来做了,but! (a+b)\%p=(a\%p+b\%p)\%p

如果a<0,先把a转化为绝对值

#include<iostream>
#include<string>
using namespace std;
typedef long long LL;
int main()
{
    int T;
    cin>>T;
    for(int kase=1;kase<=T;++kase)
    {
        string a;
        LL b,ans=0;
        cin>>a>>b;
        if(b<0) b=-b;
        int len=a.size();
        int i=0;
        if(a[0]!='-')
        {
             ans=a[0]-'0';
             i=1;
        }
        else
        {
            ans=a[1]-'0';
            i=2;
        }

        for(;i<len;++i)
            ans=(ans*10+a[i]-'0')%b;
        if(ans==0)
            cout<<"Case "<<kase<<": "<<"divisible"<<endl;
        else
             cout<<"Case "<<kase<<": "<<"not divisible"<<endl;

    }
    return 0;
}

M - Help Hanzo

题意:[a,b]区间内有多少素数  32位数10000以内区间长度判断素数个数

思路:数量太大无法打表,策略是把[a,b]区间->[0,b-a]区间,用素数筛在这个区间范围内筛素数

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int N=1e6+10;
const int M=2e6;
typedef long long LL;
int tag[N];
LL prime[M];
int interval[100010];
int cnt;
LL a,b;
void getp()
{
    memset(tag,0,sizeof(tag));
    tag[0]=tag[1]=1;
    for(int i=2;i<N;++i)
    {
        if(!tag[i])
            prime[++cnt]=i;
        for(int j=1;j<=cnt&&(LL)(i*prime[j])<N;++j)
        {
            tag[(LL)(i*prime[j])]=1;
            if(i%prime[j]==0) break;
        }
    }
}
int solve(LL a,LL b)
{
    memset(interval,0,sizeof(interval));
    for(int j=1;j<=cnt;++j)
    {
        if((LL)(prime[j]*prime[j])>b) break;   //用所有平方值小于b的prime去更新那个区间
        LL s=(a+prime[j]-1)/prime[j];          //计算第一个比a大的prime[j]的倍数是几倍  (向上取整的好操作)
        if(s<2) s=2;                           //避免把这个素数当成合数筛掉
        s*=prime[j];
        for(;s<=b;s+=prime[j])
            interval[s-a]=1;                    //把集合整到[0,b-a]
    }
    int ans=0;
    for(int i=a;i<=b;++i)
    {
        if(!interval[i-a])
            ++ans;
    }
    return ans;

}
int main()
{
    getp();
    int T;
    cin>>T;
    int kase=0;
    while(T--)
    {
        ++kase;
        cin>>a>>b;
        int ans=solve(a,b);
        if(a==1) --ans; //a=1的话在interval里面没有被筛掉
        printf("Case %d: %d\n",kase,ans);
    }
    return 0;
}

N - Trailing Zeroes (III)

题意:给出q,找出n,使得n!的十进制表达最后有q个0

题目的思路很简单,首先n!里面2和5搭配形成10,5的数目比2多,所以有几个5就能有几个10

我记得我是想暴力求出所有情况(打表),但是忽略了q是1e8的量级,这种涉及很大很大的数要用二分法

#include<iostream>
#include<map>
using namespace std;
const int N=1e8+10;
const int INF=0x3f3f3f3f;
typedef long long LL;
int Q;
LL solve(LL n)  //计算n!里有几个5
{
    LL res=0;
    while(n)
    {
        res+=n/5;           //如果是算n里面有几个5,每n/=5 加1 ,注意这个区别
        n/=5;
    }
    return res;
}

LL erfen(LL l,LL r)
{
    LL res=-1;
    while(l<=r)
    {
        LL mid=(l+r)/2;
        if(solve(mid)==Q)
        {
            res=mid;
            r=mid-1;
        }
        else if(solve(mid)>Q)
            r=mid-1;
        else
            l=mid+1;
    }
    return res;
}
int main()
{
   // cout<<INF<<endl;
    int T;
    cin>>T;
    for(int kase=1;kase<=T;++kase)
    {
        cin>>Q;
        LL left=1,right=INF;
        LL ans=erfen(left,right);
        if(ans==-1)
            cout<<"Case "<<kase<<": "<<"impossible"<<endl;
        else
            cout<<"Case "<<kase<<": "<<ans<<endl;

    }

    return 0;
}

O - GCD - Extreme (II)

题意:求出1~n之间所有数对的最大公约数之和

脑子抽了才会想去暴力。。。

f(n)=gcd(1,n)+gcd(2,n)+...+gcd(n-1,n)

s(n)=f(2)+f(3)+...+f(n-1)+f(n)=s(n-1)+f(n)

得到这个递推式后,问题转化为如何求f(n),当然是暴力遍历啦

g(n,i)表示满足gcd(x,n)=i的个数,则f(n)=\sum (i*g(n,i))

gcd(x/i,n/i)=1,所以与n的最大公约数是i的数有\phi(n/i)个,因此呼啦!最终就变成求欧拉函数了

这道题以及以往做过的数论题提醒我,筛法的思想很重要,特征是一个数由其所有约数更新,用筛法最省时(这种好像叫填表法)

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int N=4e6+10;
typedef long long LL;
int euler[N];
LL F[N];
LL G[N];
void geteu()
{
    for(int i=1;i<N;++i)
        euler[i]=i;
    for(int i=2;i<N;++i)
    {
        if(euler[i]==i)  //说明他是素数
        for(int j=i;j<N;j+=i)
            euler[j]=euler[j]/i*(i-1);
    }
}

int main()
{
    geteu();
    for(int i=1;i<N;++i)
        for(int j=2*i;j<N;j+=i)        //类似筛法的思想很重要
        F[j]+=euler[j/i]*i;
    G[2]=1;
    for(int i=3;i<N;++i)
        G[i]=G[i-1]+F[i];
//    for(int i=2;i<=10;++i)
//        cout<<G[i]<<endl;
    int n;
    while(scanf("%d",&n)&&n)
    {
        cout<<G[n]<<endl;
    }
    return 0;
}
//跑不出来原因,我的f(i)计算太慢了 相当于O(n^2)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值