训练---数论


前言

数论哇,数学这么差的我不知道能不能攻克这个难题哇!


辗转相除法

求最大公约数有一个非常有名的算法:欧几里得算法—辗转相除法

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

using namespace std

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;//0和任意一个数的最大公约数都是那个数本身
}

int main()
{
    int a,b;
    cin>>a>>b;
    cout<<gcd(a,b)<<endl;
    return 0;
}

一、等差数列

任意门
这一题的输入输出有点多,我们用scanf来读入哦!
首先已知这个是个等差数列,然后我们就利用等差数列的公式找规律,然后会发现其实找的就是最大公约数

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

using namespace std;

const int N=100010;
int a[N];

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    sort(a,a+n);
    int d=0;
    for(int i=1;i<n;i++)
        d=gcd(d,a[i]-a[0]);//a[i]-a[0]是d的倍数
      if(!d)
        printf("%d",n);
      else   printf("%d",(a[n-1]-a[0])/d+1);
    return 0;
}

算术基本定理

在这里插入图片描述
所有的正整数都可以唯一的分解成若干个质数相乘的情况(p为指数,α>0)
若要保证一个数列单调递增,那么他们的最大长度就是所有的阿尔法相加的总数。
在这里插入图片描述

筛法求素数–线性筛( O ( N ) O(N) ON

可以算出来1~n当中所有质数,以及每个数的最小质因子
①筛掉的数一定是合数,且一定是用其最小质因子筛的。
②合数一定会被筛掉。

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

using namespace std;

const int N=1000010;

int primes[N],cnt;//存所有的质数
bool st[N];//当前数有没有被筛过

int get_primes(int n)
{
    for(int i=2;i<=n;i++)//从2开始哦!
    {
        if(!st[i])primes[cnt++]=i;
        for(int j=0;primes[j]*i<=n;j++)
        {
            st[primes[j]*i]=true;
            if(i%primes[j]==0)break;//p[j]一定不大于i的所有质因子
        }
    }
}


二、X的因子链

任意门

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

using namespace std;

const int N=(1<<20)+10;//这个是不会爆int的,可以用计算机自己算算

int min_p[N];
int primes[N],cnt;//存所有的质数
bool st[N];//当前数有没有被筛过

void get_primes(int n)
{
    for(int i=2;i<=n;i++)//从2开始哦!
    {
        if(!st[i]){
                  min_p[i]=i;
            primes[++cnt]=i;
        }
  for(int j=1;primes[j]*i<=n;j++)
        {
            int t=primes[j]*i;
            st[t]=true;
            min_p[t]=primes[j];
            if(i%primes[j]==0)break;//p[j]一定不大于i的所有质因子
        }
    }
}
int sum[N];

int main()
{

    get_primes(N);
    int x;
    while(scanf("%d",&x)!=EOF)
    {//tot用于记录最大长度,k表示第i个质因子的下标

        int k=0,tot=0;
        while(x>1)
        {
            int p=min_p[x];
            sum[k]=0;
            while(x%p==0)
            {
                sum[k]++;
                tot++;
                x/=p;
            }
            k++;
        }
        //求所有质因子出现总次数的全排列
        long long res=1;
        for(int i=2;i<=tot;i++)
            res*=i;//tot!
             //去除各个质因子重复出现的次数
        for(int i=0;i<k;i++)
            for(int j=1;j<=sum[i];j++)
            res/=j;
              printf("%d %lld\n",tot,res);//输出最长序列的长度, 以及满足最大长度的序列的个数
    }

    return 0;
}

三、聪明的燕姿

任意门

约数的个数与和

在这里插入图片描述
满足这种题目要求的数是十分少的。所以我们可以爆搜+剪枝。
这个讲解讲的还蛮清楚的,我就是看这个才看懂的。

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

using namespace std;

const int N=50000;
int primes[N],cnt;
bool st[N];
int ans[N];
int len =0;

void get_primes(int n)
{
    for(int i = 2;i <= n;i++)
    {
        if(!st[i]) primes[cnt++] = i;
        for(int j = 0;primes[j]*i <= n;j++)
        {
            st[primes[j]*i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}


int is_prime(int n)
{
    if(n < N) return !st[n];//没有被筛过说明就是质数,返回true
    for(int i = 0;primes[i] <= n / primes[i];i++)
    {
        if(n % primes[i] == 0) return false;
    }
    return true;
}

void dfs(int last,int prod,int s)//last上一个用到质数的下标是多少,prod是当前这个数,s是剩下的这个数
{
    if(s==1)
    {
        ans[len++]=prod;
        return ;
    }
    if(s-1>((last<0)?0:primes[last])&&is_prime(s-1))
    {
        ans[len++]=prod*(s-1);
    }
    for(int i=last+1;primes[i]<=s/primes[i];i++)//尽量不用乘法,否则会爆int
    {
        int p=primes[i];
        for(int j=1+p,t=p;j<=s;t*=p,j+=t)
        {
            if(s%j==0)
                dfs(i,prod*t,s/j);
        }
    }
}

int main()
{
    get_primes(N-1);
    int s;
    while(cin>>s)
    {
        len=0;
        dfs(-1,1,s);//因为我们的下标是从0开始
                cout<<len<<endl;
                if(len)
         {
               sort(ans,ans+len);
               for(int i=0;i<len;i++)
               cout<<ans[i]<<' ';
                cout<<endl;
         }
    }
    return 0;
}

裴属定理—扩展欧几里得定理

如果对于正整数a,b有他们的最大公约数d,那么必定存在一组系数,ax+by=d。欧几里得是求出d来,扩展欧几里得是求出x,y来,得到一组解。
在这里插入图片描述
只要知道一组解,那么所有的解都可以求出来了。
在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

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

int main()
{
	int a,b,x,y;
	cin>>a>>b;
	int d=exgcd(a,b,x,y);
	printf("%d * %d +%d * %d =%d",a,x,b,y,d);
	return 0;	
} 

四、五指山

任意门
在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
ll n,d,x,y,a,b;
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;
}

int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld%lld%lld%lld",&n,&d,&x,&y);
		int gcd=exgcd(n,d,a,b);
		if((y-x)%gcd)puts("Impossible");
		else
        {
            b*=(y-x)/gcd;
            n/=gcd;
            printf("%lld\n",(b%n+n)%n);//这样子一定可以得到一个正数
        }
	}
	return 0;
}

五、最大比例

任意门
相当于等比数列告诉你各个数,然后求其最大等比
在这里插入图片描述
排序+判重
在这里插入图片描述
求k最大值的情况。

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N=110;

int n;
ll x[N],a[N],b[N];


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

ll gcd_sub(ll a,ll b)
{
    if(a>b)swap(a,b);
    if(b==1)return a;
    return gcd_sub(b,a/b);
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>x[i];
    sort(x,x+n);//排序
    int cnt=0;
    for(int i=1;i<n;i++)//去重
        if(x[i]!=x[i-1])
    {
        ll d=gcd(x[i],x[0]);
        a[cnt]=x[i]/d;
        b[cnt]=x[0]/d;
        cnt++;
    }
    ll up=a[0],down=b[0];
    for(int i=1;i<cnt;i++)
    {
        up=gcd_sub(up,a[i]);
        down=gcd_sub(down,b[i]);
    }
    cout<<up<<"/"<<b<<endl;
    return 0;
}

六、C循环

任意门
可以将式子推出来像下面这个样子。
在这里插入图片描述
我们就可以用扩展欧几里得的式子将系数求出来。在这里插入图片描述
exgcd的变化过程。
在这里插入图片描述

#include<bits/stdc++.h>

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;
}

int main()
{
    ll a,b,c,k,res;
  while(cin>>a>>b>>c>>k,a||b||c||k)//逗号表达式,只会看最后一个表达式的值
  {
      ll x,y;
      ll mod=1ll<<k;
      ll d=exgcd(c,mod,x,y);//扩展欧几里得
      if((b-a)%d)cout<<"FOREVER"<<endl;
      else {
        x*=(b-a)/d;
        mod/=d;
        cout<<(x%mod+mod)%mod<<endl;//这样子就保证了他是正数
      }
  }
    return 0;
}

pow求出来不是一个精确的值!

七、正则问题(递归)

任意门
所有的递归问题都可以看成一棵树。
难点在于建树。笑死,这怎么想的到哈哈哈哈
学数论逐渐癫狂

#include<bits/stdc++.h>

using namespace std;

int k;
string str;
int dfs()
{
    int res=0;
    while(k<str.size())
    {
        if(str[k]=='(')//处理(....)
        {
            k++;//跳过左括号
            res+=dfs();
            k++;//跳过右括号
        }
        else if(str[k]=='|')
            {
                k++;//跳过'|'
                res=max(res,dfs());
            }
            else if(str[k]==')')break;//这里是个坑,很容易写成k++
            else{
                k++;//跳过x
                res++;
            }
    }
    return res;
}

int main()
{
    cin>>str;
    cout<<dfs()<<endl;
    return 0;
}

八、糖果(爆搜,递归!!!)

任意门
这一道题目,不需要考虑个数,只需要考虑有没有。
模拟样例:
在这里插入图片描述
最少选几行使得每一列至少存在一个1
这题数据不是很大

这是一个经典的题目:重复覆盖问题

(还有一个精确覆盖问题:八皇后、数独)
Dancing links来做这两类题目
但是我们这里用爆搜,因为数据量很小
①顺序
{
经典优化,迭代加深(我们首先枚举,只需要1行能不能得到答案,如果不行,两行。。。以此类推)
}
②将选择最少的列
③可行性剪枝(至少要选多少行,我们还可以用二进制来进行有哈)

lowbit()可以返回x的最后一位1

写法一:这是y总的写法

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 110, M = 1 << 20;

int n, m, k;
vector<int> col[N];
int log2[M];

int lowbit(int x)
{
    return x & -x;
}

int h(int state)  // 最少需要再选几行
{
    int res = 0;
    for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
    {
        int c = log2[lowbit(i)];
        res ++ ;
        for (auto row : col[c]) i &= ~row;
    }
    return res;
}

bool dfs(int depth, int state)
{
    if (!depth || h(state) > depth) return state == (1 << m) - 1;

    // 找到选择性最少的一列
    int t = -1;
    for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
    {
        int c = log2[lowbit(i)];
        if (t == -1 || col[t].size() > col[c].size())
            t = c;
    }

    // 枚举选哪行
    for (auto row : col[t])
        if (dfs(depth - 1, state | row))
            return true;

    return false;
}

int main()
{
    cin >> n >> m >> k;

    for (int i = 0; i < m; i ++ ) log2[1 << i] = i;
    for (int i = 0; i < n; i ++ )
    {
        int state = 0;
        for (int j = 0; j < k; j ++ )
        {
            int c;
            cin >> c;
            state |= 1 << c - 1;
        }

        for (int j = 0; j < m; j ++ )
            if (state >> j & 1)
                col[j].push_back(state);
    }

    int depth = 0;
    while (depth <= m && !dfs(depth, 0)) depth ++ ;

    if (depth > m) depth = -1;
    cout << depth << endl;

    return 0;
}

写法二:01背包+状态压缩

某友写的哦!

//时间复杂度为O(n * 2^m) 为1e8勉强能过
//状态表示:dp[i][s]为从前i个糖果中选,选出来的糖果种类状态为s的所需最少糖果数量
//阶段划分:类似于01背包问题中,从前i个物品中选
//转移方程:划分依据:对于第i个物品选还是不选
//dp[i][s] = min(dp[i-1][s],dp[i-1][s & (~w[i])] + 1) 
//dp[i-1][s]好说 表示不选第i个物品
//关键是dp[i-1][s & (~w[i])],举个例子若现在s为 11011,表示已经选出来的糖果种类为1,2,8,16
//假设第i包糖果的状态为01011,那么他是从10000这个状态转移过来的
//那么s & (~w[i])的目的就是先将w[i]的糖果种类去掉,~w[i]按位取非,在与s相于就行了,实质上就是s & (w[i]在s中的补集)

//边界:dp初始化为INF dp[0][0] = 0;
//目标:dp[n][(1<<m)-1]
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110,M = 1 << 20,INF = 0x3f3f3f3f;
int w[N],dp[M],n,m,k;//注意优化成一维不然暴空间
int main(void)
{
    cin>>n>>m>>k;
    for(int i = 1;i<=n;i++) 
        for(int j = 1;j<=k;j++)
        {
            int num;
            cin>>num;
            w[i] |= (1 << num - 1);//将每一件糖果所包含的糖果种类压缩
        }

    memset(dp,0x3f,sizeof dp);

    dp[0] = 0;

    for(int i = 1;i<=n;i++)
        for(int s = 0;s < (1 << m);s ++) dp[s] = min(dp[s],dp[s & (~w[i])] + 1);

    if(dp[(1<<m)-1] == INF) cout<<-1<<endl;
    else cout<<dp[(1<<m) - 1]<<endl;

    return 0;
}

写法三:状态压缩

牛逼!!这是我觉得写的最好的版本!

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

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, k;
int can[100][20], dp[1 << 20];

int main(void){
    cin >> n >> m >> k;
    memset(can, 0, sizeof(can));
    memset(dp, -1, sizeof(dp));
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < k; ++j){
            cin >> can[i][j];
            can[i][j]--;
        }


    int lim = 1 << m;
    dp[0] = 0;
    for (int i = 0; i < n; ++i){
        int t = 0;
        for (int j = 0; j < k; ++j) t |= (1 << can[i][j]);
        for (int st = 0; st < lim; ++st){
            if (dp[st] == -1) continue;
            int nst = st | t;
            if (dp[nst] == -1 || dp[nst] > dp[st] + 1) dp[nst] = dp[st] + 1;
            // printf("(%d, %d) ", nst, dp[nst]);
        }
        // printf("\n");
    }

    cout << dp[lim - 1];
    return 0;
}

总结

果然很痛苦!
真的会谢!
最后一题,我真的觉得很妙,但是目前的我是真的干不来哈哈哈哈,所以希望以后我回顾我写的这些博客的时候,我能够常看看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值