训练---数学与简单dp

一、买不到的数(数学)

任意门
这道题记住结论就可以了。想这类题目,我们可以运用打表来找到结论。
(数论) O ( 1 ) O(1) O(1)

结论:

如果 a,b=均是正整数且互质,那么由 ax+by,x≥0,y≥0不能凑出的最大数是 ab−a−b。

下面给出证明:

首先证明 a b − a − b ab−a−b abab 不能被 a x + b x , x ≥ 0 , y ≥ 0 ax+bx,x≥0,y≥0 ax+bx,x0,y0表示出。 反正法,假设 a b − a − b = a x + b y ab−a−b=ax+by abab=ax+by,那么
a b = a ( x + 1 ) + b ( y + 1 ) ab=a(x+1)+b(y+1) ab=a(x+1)+b(y+1),由于 a ∣ a b , a ∣ a ( x + 1 ) a|ab,a|a(x+1) aab,aa(x+1),所以 a ∣ b ( y + 1 ) a|b(y+1) ab(y+1),由于 a , b a,b a,b 互质,所以
a ∣ ( y + 1 ) a|(y+1) a(y+1),由于 y ≥ 0 y≥0 y0,所以 a < = y + 1 a<=y+1 a<=y+1,所以 b ( y + 1 ) ≥ a b b(y+1)≥ab b(y+1)ab。同理可得 a ( x + 1 ) ≥ a b a(x+1)≥ab a(x+1)ab,所以
a ( x + 1 ) + b ( y + 1 ) ≥ 2 a b > a b a(x+1)+b(y+1)≥2ab>ab a(x+1)+b(y+1)2ab>ab,矛盾。

证明 a b − a − b + d , d > 0 ab−a−b+d,d>0 abab+d,d>0 一定可以表示成 a x + b y , x , y ≥ 0 ax+by,x,y≥0 ax+by,x,y0 的形式。

时间复杂度分析
计算 a b − a − b ab−a−b abab 的时间复杂度是 O ( 1 ) O(1) O(1)

#include<iostream>

using namespace std;

int main()
{
    int n,m;
    cin>>n>>m;
    cout<<(n-1)*(m-1)-1;
    return 0;
}

二、蚂蚁感冒(数学)

任意门
虽然题意是两个蚂蚁相撞就会往回走,但是我们可以把掉头等价为穿过。这种等价转化的方法可以是问题很好理解。

我们以第一个蚂蚁向左走为例:将蚂蚁分为左边一块和右边一块,左边一块的左边一定是安全的,右边一块的右边也一定是安全的。左边的向左走如果没有感冒的蚂蚁向左走说明左边向右走的蚂蚁是安全的,否则必然全部感染,右边向右走的必然会感染。

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

using namespace std;

const int N=55;

int n;
int x[N];

int main()
{
    cin>>n;
    for(int i=0; i<n; i++)cin>>x[i];
    int left=0,right=0;//分别表示左边向右走的蚂蚁数量和右边向左走的蚂蚁数量
    for(int i=1; i<n; i++)
    {
        if(abs(x[i])<abs(x[0])&&x[i]>0)left++;
        else if(abs(x[i])>abs(x[0])&&x[i]<0)right++;
    }
      if((x[0]>0&&right==0)||(x[0]<0&&left==0))
        cout<<1<<endl;
      else cout<<left+right+1<<endl;
    return 0;
}

三、饮料换购(数学、模拟)

任意门
我们要慢慢分析,像这种题目就是考察数学,考察一定的思维,所以我们在分析的时候一定要慢慢的分析,不要太乱了。
这一道题的n不是很大,模拟和队列都是可以的。但是我们运用这个的方法会快一些。

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

using namespace std;



int main()
{
    int n;
    cin>>n;
    int res=n;
    while(n>=3)
    {
        res+=n/3;//这里运用到了下取整
        n=n/3+n%3; //前面是能换够的合数,后面是盖子的个数       
    }
    cout<<res;
    return 0;
}

上取整和下取整

① 上下取整的话本来就有公式,ceil()上取整,floor()下取整。
②a/b的上取整和(a+b-1)/b的下取整是一样的哦!

四、01背包问题(dp)

这里就参考到了y式dp法。
首先出场的是朴素算法,这是他的暴力写法(二维空间写法)

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

using namespace std;

const int N=1010;
int n,m;
int f[N][N];
int v[N],w[N];


int main()
{
   cin>>n>>m;
   for(int i=1;i<=n;i++)
    cin>>v[i]>>w[i];
   for(int i=1;i<=n;i++)
   for(int j=0;j<=m;j++)
   {
       f[i][j]=f[i-1][j];//左半边的子集
       if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
   }
   cout<<f[n][m]<<endl;
    return 0;
}

优化
dp的优化都是对代码进行等价运行!

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

using namespace std;

const int N=1010;
int n,m;
int f[N];
int v[N],w[N];


int main()
{
   cin>>n>>m;
   for(int i=1;i<=n;i++)
    cin>>v[i]>>w[i];
  for(int i=1;i<=n;i++)
   for(int j=m;j>=v[i];j--)
   //从大到小就可以保证是f[i-1][j-v[i]]
   //保证了先更新f[i]后更新f[j-v[i]],且后更新的是上一层的
        f[j]=max(f[j],f[j-v[i]]+w[i]);//看一下右边是不是比左边大
        cout<<f[m]<<endl;
    return 0;
}

五、摘花生(dp)

一个方格,每一次只能向下或者是向右走。
这道题目是基于数字三角形来写的(线性dp)。

数字三角形dp代码:一个数字三角形,寻找一个路径,使其的数字之和最大。从底向上进行寻找!

第一个版本代码(竟然是我自己之前写的)

 #include <iostream>
#include <algorithm>
#define MAX 1000000000
int D[501][501];
int n;
int maxSum[501][501];

using namespace std;

int MaxSum(int i,int j)
{
    if(maxSum[i][j]!=-1)
        return maxSum[i][j];
    if(i==n)
        maxSum[i][j]=D[i][j];
    else {
        int x=MaxSum(i+1,j);
        int y=MaxSum(i+1,j+1);
        maxSum[i][j]=max(x,y)+D[i][j];
    }
    return maxSum[i][j];
}


int main()
{
    int i,j;
    cin>>n;
    for(i=1;i<=n;i++)
        for(j=1;j<=i;j++)
    {
        cin>>D[i][j];
        maxSum[i][j]=-1;
    }
    cout<<MaxSum(1,1)<<endl;
    return 0;
}

第二个版本代码

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

using namespace std;
const int N=510;
int a[N][N],b[N][N];

int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) 
    for(int j=1;j<=i;j++)
        cin>>a[i][j];
    for(int i=1;i<=n;i++)
        b[n][i]=a[n][i];
    for(int i=n-1;i;i--)
        for(int j=1;j<=i;j++)
       b[i][j]=max(b[i+1][j],b[i+1][j+1])+a[i][j];
    cout<<b[1][1]<<endl;
    
    return 0;
}

摘花生代码!!!

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

using namespace std;
const int N=110;

int n,m;
int w[N][N];
int f[N][N];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            scanf("%d",&w[i][j]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                f[i][j]=max(f[i-1][j]+w[i][j],f[i][j-1]+w[i][j]);//可以把w[i][j]提出来
            printf("%d\n",f[n][m]);
    }
       
    return 0;
}

六、最长上升子序列(dp)

任意门

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

using namespace std;

const int N=1010;
int a[N],f[N];

int main()
{
    int n,res;
    cin>>n;
    for(int i=0; i<n; i++)
        cin>>a[i];
    for(int i=0; i<n; i++)
    {
        f[i]=1;
        for(int j=0; j<i; j++)
        {
            if(a[j]<a[i])
                f[i]=max(f[i],f[j]+1);
        }
        res=max(res,f[i]);
    }
    cout<<res<<endl;
    return 0;
}


七、地宫取宝(dp)

任意门
这道题规则:
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
这道题相当于把前面三道题都给几个限制放在一起了:①要从左上角走到右下角②要按递增的顺序依次拿宝贝③只能拿k件,要求总价值最大。

MOD=1000000007这道题的MOD有点大,当两个MOD相加的时候还不会爆int,但是三个数相加就会,所以我们在两个数相加之后就必须要取余了!

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

using namespace std;

const int N=55,MOD=1000000007;
int n,m,k;
int w[N][N];
int f[N][N][13][14];


int main()
{
   cin>>n>>m>>k;
   for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
       {
           cin>>w[i][j];
           w[i][j]++;
       }
       //第一个位置我们选还是不选
       f[1][1][1][w[1][1]]=1;
       f[1][1][0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
    {
        if(i==1&&j==1)continue;
        for(int u=0;u<=k;u++)
            for(int v=0;v<=13;v++)
        {
            int &val=f[i][j][u][v];
            val=(val+f[i-1][j][u][v])%MOD;
            val=(val+f[i][j-1][u][v])%MOD;
            if(u>0&&v==w[i][j])
            {
                for(int c=0;c<v;c++)
                {
                    val=(val+f[i-1][j][u-1][c])%MOD;
                    val=(val+f[i][j-1][u-1][c])%MOD;

                }

            }
        }
    }
    int res=0;
    for(int i=0;i<=13;i++)res=(res+f[n][m][k][i])%MOD;
    cout<<res<<endl;
    return 0;
}


这道题还是要多理解一下,有点难呜呜呜–

八、波动数组(dp)

任意门

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

using namespace std;

const int N=1010,MOD=100000007;

int f[N][N];

int get_mod(int a,int b)//一定可以的到a/b的一个正余数
{
    return (a%b+b)%b;
}

int main()
{
    int n,s,a,b;
    cin>>n>>s>>a>>b;
    f[0][0]=1;
    for(int i=1;i<n;i++)
        for(int j=0;j<n;j++)
        f[i][j]=(f[i-1][get_mod(j-a*i,n)]+f[i-1][get_mod(j+b*i,n)])%MOD;
        cout<<f[n-1][get_mod(s,n)]<<endl;
    
    return 0;
}

dp的模型不是很多,然后蓝桥杯考的也不会特别的难,所以最重要的就是背下来。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值