数论刷题部分集锦

持续更新ing~
acwing198质数距离
题意:给定一个接近int极限的大区间,求这个区间内差值最小的两个相邻素数以及差值最大的两个素数,打印出来,没有这样的素数对,就打印“There are no adjacent primes.”。
分析:一看区间范围,1≤L<U≤2^31−1,太大了,如果直接筛法素数打表,毫无疑问,数组肯定开不起来,那么我们得换个思路想想,怎么才能得到一个大区间内的大素数呢?这个时候就需要用到区间筛,区间筛就是用来解决一个大区间内大素数问题,因为当数据范围太大的时候数组开不起来,这个时候不能直接筛法开数组存素数,首先,对于某个数n,当n非常大的时候求其区间内的素数是非常困难的,复杂度很高,但我们可以把问题转化一下,我们知道对于一个合数,若d为它的一个因子,显然n/d也是它的因子,那么d<=sqrt(n),这是显而易见的,因此,我们可以简化到sqrt(n),用筛法打[2,sqrt(n)]的素数表,利用这个表,将给出区间[a,b]范围内的素数表中的素数的倍数给划去,那么剩下的就都是素数了,利用这种思想来帮助我们解题就轻松很多了
ac代码:

#pragma GCC optimize(3)
#pragma GCC optimize(2)
#pragma GCC optimize("Ofast")
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;

typedef long long ll;

const int maxn=1e6+10;
int primes[maxn],cnt;
bool vis[maxn];


//线性筛打表[2,sqrt(n)]
void get(int n)
{
    cnt=0;
    memset(vis,false,sizeof(vis));
    for(int i=2;i<=n;i++){
        if(!vis[i])primes[cnt++]=i;
        for(int j=0;i*primes[j]<=n/i;j++){
            vis[i*primes[j]]=true;
            if(i%primes[j]==0)break;
        }
    }
}

//区间筛
void get(int l,int r)
{
    memset(vis,false,sizeof(vis));//一定要清零
    for(int i=0;i<cnt;i++){
        ll p=primes[i];
        for(ll j=max(p+p,(l+p-1)/p*p);j<=r;j+=p){//之所以取max是为了节省时间取到区间l到r内出现的第一个p的倍数
            vis[j-l]=true;//之所以要减去l是为了防止数组下标溢出
        }
    }
    cnt=0;
    for(int i=0;i<=r-l;i++){
        if(!vis[i]&&i+l>=2)primes[cnt++]=i+l;//把区间l到r内筛完以后得到的素数赋到数组primes中去,且要防止边界情况l<2
    }
    if(cnt<2)printf("There are no adjacent primes.\n");
    else{
        int minv=0,maxv=0;
        for(int i=0;i+1<cnt;i++){
            int d=primes[i+1]-primes[i];
            if(d<primes[minv+1]-primes[minv])minv=i;
            if(d>primes[maxv+1]-primes[maxv])maxv=i;
        }
        printf("%d,%d are closest, %d,%d are most distant.\n",primes[minv],primes[minv+1],primes[maxv],primes[maxv+1]);
    }
    
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int a,b;
    while(cin>>a>>b){
        get(50010);
        get(a,b);
    }
    return 0;
}

hdu4704 费马小定理+组合公式
题意+思路:给出一个数N,当N只由一位数组成时的组合数为S1,由两位数组成时的组合数为S2,…,一直到N位数,将所有组合数加起来即得所求值,例如N=2,由一位数组成时只有一种方案:2,两位数时也只有一种:1+1,因此总组合数为2,输出2,N=3的时候:一位数一种:3;两位数2种:1+2,2+1;三位数1种:3;因此N=3的时候输出4;以此类推N=4时输出8,N=5时输出16,这样一直到n时应当是2^(n-1)。(其实也可以不用这么手动去推,知道隔板原理的话就可以直接运用组合公式来解)但是由于n的范围实在是太大了,直接输出答案不仅会爆int,longlong一样的爆掉,又因为1e9+7和2互质,因此利用费马小定理进行降幂,但这道题并没有那么简单,它还有一个非常坑的点开始我没考虑到,就是因为n太大了,没有办法直接读入,因此要利用字符串进行数据读入,然后再转化成llong整数,且转化一位都要取一次模,不然肯定会爆掉
ac代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<string>

using namespace std;

typedef long long ll;
const ll _mod=1000000007;

ll quick_pow(ll x,ll y,ll mod)
{
    ll ans=1;
    while(y){
        if(y&1)ans=(ans*x)%mod;
        x=(x*x)%mod;
        y>>=1;
    }
    return ans%mod;
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    string s;
    while(cin>>s){
        ll n=0;
        for(int i=0;i<s.length();i++){
            n=(10*n+(s[i]-'0'))%(_mod-1);
        }
        ll ans=quick_pow(2,(n-1),_mod);
        cout<<ans<<endl;
    }
    //system("pause");
    return 0;
 }

hdu1370
中国剩余定理互素版模板题
(注意输出的必须是最小正整数,不能输出0)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

typedef long long ll;

ll a[3]={0},m[3]={23,28,33},n=3;

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b){
        x=1,y=0;
        return a;
    }
    ll d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}

ll CRT(ll a[],ll m[],ll n)
{
    ll M=1,ans=0,x,y;
    for(int i=0;i<n;i++)M*=m[i];
    for(int i=0;i<n;i++){
        ll t=M/m[i];
        exgcd(t,m[i],x,y);
        ans=(ans+a[i]*t*x)%M;
    }
    return (ans%M+M)%M;
}

int main()
{
    ll t,p,e,i,d;
    scanf("%lld",&t);
    while(t--){
        int kase=0;
        while(~scanf("%lld%lld%lld%lld",&p,&e,&i,&d)&&p!=-1){
            a[0]=p,a[1]=e,a[2]=i;
            ll x=CRT(a,m,n);
            while(x<=d)x+=23*28*33;
            printf("Case %d: the next triple peak occurs in %lld days.\n",++kase,x-d);
        }
        //printf("\n");其实我想我没看错的话题目里的确说了输入块之间是需要打印空白行的,但是提交的时候如果不注释掉这行的话是会pe的
    }
    return 0;
}

hdu3579
中国剩余定理不互素版模板题(注意输出的必须是最小正整数,不能输出0)
ac:

#include<iostream>
#include<cstdio>

using namespace std;

typedef long long ll;

const int N=10;
ll a[N]={0},m[N]={0};

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b){
        x=1,y=0;
        return a;
    }
    ll d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}

ll gcd(ll a,ll b)
{
    return b==0?a:gcd(b,a%b);
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;
    cin>>t;
    for(int i=1;i<=t;i++){
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++)cin>>m[i];
        for(int i=0;i<n;i++)cin>>a[i];
        ll a1=a[0],m1=m[0],a2,m2,k1,k2,lcm=m[0];
        for(int i=1;i<n;i++){
            a2=a[i],m2=m[i];
            ll d=exgcd(m1,m2,k1,k2);
            if((a2-a1)%d){a1=-1;break;}
            k1*=(a2-a1)/d;
            ll t=m2/d;
            k1=(k1%t+t)%t;
            a1+=k1*m1;
            m1*=t;
            lcm=lcm/gcd(lcm,m[i])*m[i];
        }
        if(!a1)a1=lcm;
        cout<<"Case "<<i<<": "<<a1<<endl;
    }
    return 0;
}


hdu2104
题意转化:从当前点开始每次走m步,能不能把0~n-1区间内所有的数都遍历一遍,能遍历完就等价于能找到手帕,打印“YES”,否则“POOR Haha”
思路:要取到0~n-1之间的所有的数当且仅当m和n互素,即gcd(n,m)=1,因此这道题就转化成了一道判断互素的题

#include<cstdio>

using namespace std;

typedef long long ll;

/*ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b){
        x=1,y=0;
        return a;
    }
    ll d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}*/

ll gcd(ll a,ll b)
{
    return b==0?a:gcd(b,a%b);
}

int main()
{
    ll a,b;
    while(~scanf("%lld%lld",&a,&b)){
        if(a==-1&&b==-1)break;
        ll d=gcd(a,b);
        if(d==1)puts("YES");
        else puts("POOR Haha");
    }
    return 0;
}

hdu5019
题意:给出x和y,找出x和y第k大的公因子
思路:题目数据范围1e12,非常大,显然不能把两个数的所有公因子都一个一个列出来,因此寻求简化,因为两个数的公因子都是它们最大公因数的因子,因此可以列举到sqrt(gcd(x,y)),这样最坏的时候也不过是1e6
ac 670ms

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cmath>

using namespace std;

typedef long long ll;

bool cmp(ll a,ll b)
{
    return a>b;
}

ll gcd(ll a,ll b)
{
    return b==0?a:gcd(b,a%b);
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        ll x,y,k;
        cin>>x>>y>>k;
        vector<ll> v;
        ll d=gcd(x,y);
        for(int i=1;i<=sqrt(d);i++){
            if(d%i==0){
                v.push_back(i);
                if(i!=d/i)v.push_back(d/i);
            }
        }
        sort(v.begin(),v.end(),cmp);
        if(v.size()>=k)cout<<v[k-1]<<endl;
        else puts("-1");
    }
    return 0;
}

(ps:开始老是过不了,重写一遍就过了,明明没变什么,太奇怪了)

hdu4497
题意:
思路:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值