C语言数学问题与简单DP背包问题详解

目录
  • 数学
    • 买不到的数目
    • 蚂蚁感冒
    • 饮料换购
  • 简单DP
    • 背包问题
      • 二维
      • 一维

数学

顾名思义,数学类的题就是都可以用数学知识求解。

买不到的数目

这是第四届蓝桥杯省赛C++A组,第四届蓝桥杯省赛JAVAC组的一道题

小明开了一家糖果店。

他别出心裁:把水果糖包成4颗一包和7颗一包的两种。

糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。

当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。

大于17的任何数字都可以用4和7组合出来。

本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。

输入格式
两个正整数 n,m,表示每种包装中糖的颗数。

输出格式
一个正整数,表示最大不能买到的糖数。

数据范围
2≤n,m≤1000,
保证数据一定有解。

输入样例:

4 7

输出样例:

17

这道题简单看一下,似乎没有什么规律,我们可以先打表来找一下规律:

#include <bits/stdc++.h>

using namespace std;

//给定一个m,是否能用p和q凑出来
bool dfs(int m,int p,int q)
{
    if(m == 0) return true;

    if(m >= p && dfs(m - p,p,q)) return true;
    if(m >= q && dfs(m - q,p,q)) return true;

    return false;
}

int main()
{
    int p,q;
    cin >> p >> q;
    int res = 0;
    for(int i = 1; i <= 1000;i ++)
    {
        if(!dfs(i,p,q)) res = i;
    }

    cout << res << endl;

    return 0;
}

打表暴力搜索找规律

2 3 输出 1
3 5 输出7
3 7 输出11
3 10 输出17
...
最后得到规律
如果 a,b均是正整数且互质,那么由 ax+by,x≥0,y≥0ax+by,x≥0,y≥0 不能凑出的最大数是 (a−1)(b−1)−1。

接下来再看数学代码:

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int p, q;
    cin >> p >> q;
    cout << (p - 1) * (q - 1) - 1 << endl;

    return 0;
}

蚂蚁感冒

这也是蓝桥杯的一道题,来源:第五届蓝桥杯省赛C++A/B组

长 100 厘米的细长直杆子上有 n 只蚂蚁。

它们的头有的朝左,有的朝右。

每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒。

当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。

这些蚂蚁中,有 1 只蚂蚁感冒了。

并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。

请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。

输入格式
第一行输入一个整数 n, 表示蚂蚁的总数。

接着的一行是 n 个用空格分开的整数 Xi, Xi 的绝对值表示蚂蚁离开杆子左边端点的距离。

正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。

其中,第一个数据代表的蚂蚁感冒了。

输出格式
输出1个整数,表示最后感冒蚂蚁的数目。

数据范围
1<n<50,
0<|Xi|<100

输入样例1:

3
5 -2 8

输出样例1:

1

输入样例2:

5
-10 8 -20 12 25

输出样例2:

3

这题很有意思,只读题就会有这种想法,如果一只蚂蚁从左往右走,另外一只从右往左走,有一只感冒了,那么,他们相遇后就会分别向相反的方向走。

按照这个思路,我们来写一个暴力解法:

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n, pos;
int a[N];
int ans = 1;
int cmp(int a, int b)
{
  return abs(a) < abs(b);
}
int main()
{
  cin >> n;
  for (int i = 0; i < n; i++)
    cin >> a[i];
  int pre = a[0];
  sort(a, a + n, cmp); //先按绝对值 将蚂蚁的位置排好
  for (int i = 0; i < n; i++)
  {
    if (a[i] == pre)
      pos = i;
  }
  int flag = 0;
  if (pre > 0) //首先感染的蚂蚁向右走
  {
    for (int i = pos + 1; i < n; i++)
    {
      if (a[i] > 0)
        continue;
      if (a[i] < 0)
      {
        ans++;
        flag = 1; //标记右面有蚂蚁向左走
      }
    }
    for (int i = pos - 1; i >= 0; i--)
    {
      if (flag) //在右边有往左走的蚂蚁前提下
      {
        if (a[i] > 0) //如果左面有向右走的那么肯定会传染
          ans++;
      }
    }
  }
  if (pre < 0)  //首先感染的蚂蚁向左走,方法同上
  {
    for (int i = pos - 1; i >= 0; i--)
    {
      if (a[i] < 0)
        continue;
      if (a[i] > 0)
      {
        ans++;
        flag = 1;
      }
    }
    for (int i = pos + 1; i < n; i++)
    {
      if (a[i] > 0)
        continue;
      if (flag)
      {
        if (a[i] < 0)
          ans++;
      }
    }
  }
  cout << ans << endl;

  return 0;
}

但这中间就有一个很有意思的地方就是左边的往右走,右边的往左走,有一只感冒了,它们相遇后还是等价于有两只蚂蚁分别往前走,只是这样两只蚂蚁都感冒了,这样之后遇到的蚂蚁也会被感冒,这样想就不会有掉头做判断这一步了,接下来请看代码:

#include <bits/stdc++.h>

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

饮料换购

来源:第六届蓝桥杯省赛C++A/C组,第六届蓝桥杯省赛JAVAB组

乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。

请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n 瓶饮料,最后他一共能喝到多少瓶饮料。

输入格式
输入一个整数 n,表示初始买入的饮料数量。

输出格式
输出一个整数,表示一共能够喝到的饮料数量。

数据范围
0<n<10000

输入样例:

100

输出样例:

149

这题就很简单了,还是先看模拟代码:

#include <bits/stdc++.h>

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

    return 0;
}

然后是数学公式代码:

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int n;
    cin >> n;
    cout << n + (n - 1) / 2 << endl;
    return 0;
}

简单DP

先来看题:背包问题是非常经典的DP问题。

背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

题目介绍

有 N 件物品和一个容量为 V的背包,每件物品有各自的价值且只能被选择一次,要求在有限的背包容量下,装入的物品总价值最大。

「0-1 背包」是较为简单的动态规划问题,也是其余背包问题的基础。

动态规划是不断决策求最优解的过程,「0-1 背包」即是不断对第 i个物品的做出决策,「0-1」正好代表不选与选两种决定

二维

(1)状态f[i][j]定义:前 i个物品,背包容量 j 下的最优解(最大价值):

当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N 件物品,则需要 N 次决策,每一次对第 i 件物品的决策,状态f[i][j]不断由之前的状态更新而来。
(2)当前背包容量不够(j < v[i]),没得选,因此前 i个物品最优解即为前 i−1个物品最优解:

对应代码:f[i][j] = f[i - 1][j]。
(3)当前背包容量够,可以选,因此需要决策选与不选第 i 个物品:

选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。
我们的决策是如何取到最大价值,因此以上两种情况取 max() 。

接下来请看二维求解代码:

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 1005;
int v[MAXN];    // 体积
int w[MAXN];    // 价值
int f[MAXN][MAXN];  // f[i][j], j体积下前i个物品的最大价值 

int main()
{
    int n, m;
    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 = 1; j <= m; j++)
        {
            //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i])
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

    cout << f[n][m] << endl;

    return 0;
}

一维

将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形。

原式:f[i][j] = max(f[i-1][j], f[i - 1][j - v[i]] + w[i]);

改成一维:f[j] = max(f[j], f[j - v[i]] + w[i]);

先来看代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 1010;

int f[N];
int v[N], w[N];
int n, m;
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[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

    cout << f[m] << endl;
}

这中间有一个点大家可能不太好理解,为什么是for (int j = m; j >= v[i]; j--),而不是for (int j = v[i]; j <= m; j++)

首先我们先来模拟一下如果是for (int j = v[i]; j <= m; j++),循环就是:

for (int i = 1; i <= n; i++) {
        for (int j = v[i]; j <= m; j++) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

来看题

输入样例

4 5
1 2
2 4
3 4
4 5
物品     体积     价值
i=1           1           2
i=2          2           4
i=3          3           4
i=4          4           5

当还没进循环的时候

f[0] = 0;  f[1] = 0;  f[2] = 0;  f[3] = 0;  f[4] = 0;  f[5] = 0;

当进入循环 i == 1 时:v[i]=1,w[i]=2;

j=1:f[1]=max(f[1],f[1-1]+2),即max(0,2)=2;即f[1]=2;
j=2:f[2]=max(f[2],f[2-1]+2),即max(0,4)=4;即f[2]=4;
j=3:f[3]=max(f[3],f[3-1]+2),即max(0,6)=6;即f[3]=6;
j=4:f[4]=max(f[4],f[4-1]+2),即max(0,8)=8;即f[4]=8;
j=5:f[5]=max(f[5],f[5-1]+2),即max(0,10)=10;即f[5]=10;

当到这里的时候就已经出问题了。

//如果 j 层循环是逆序的: for (int i = 1; i <= n; i++) {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E--> for (int j = m; j >= v[i]; j--) {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E--> f[j] = max(f[j], f[j - v[i]] + w[i]); } }//如果 j 层循环是逆序的:
    for (int i = 1; i <= n; i++) {
        for (int j = m; j >= v[i]; j--) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

输入样例

4 5
    1 2
    2 4
    3 4
    4 5
    物品     体积        价值
    i=1           1        2
    i=2          2        4
    i=3          3        4
    i=4          4        5

当还没进循环的时候
    f[0] = 0;  f[1] = 0;  f[2] = 0;  f[3] = 0;  f[4] = 0;  f[5] = 0; 
    当进入循环时i==1,w[i]=2,v[i]=1;
    j=5:f[5]=max(f[5],f[5-1]+2),即max(0,2)=2;即f[5]=2;
    j=4:f[4]=max(f[4],f[4-1]+2),即max(0,2)=2;即f[4]=2;
    j=3:f[3]=max(f[3],f[3-1]+2),即max(0,2)=2;即f[3]=2;
    j=2:f[2]=max(f[2],f[2-1]+2),即max(0,2)=2;即f[2]=2;
    j=1:f[1]=max(f[1],f[1-1]+2),即max(0,2)=2;即f[1]=2;
    当进入循环 i == 2 时,w[i]=4,v[i]=2;
    j=5:f[5]=max(f[5],f[5-2]+4),即max(2,6)=6,即f[5]=6;
    j=4:f[5]=max(f[4],f[4-2]+4),即max(2,6)=6,即f[4]=6;
    j=3:f[5]=max(f[3],f[3-2]+4),即max(2,6)=6,即f[3]=6;
    j=2:f[5]=max(f[2],f[2-2]+4),即max(2,4)=4,即f[2]=4;
    当进入循环 i == 3 时: w[i] = 4; v[i] = 3;
    j=5:f[5]=max(f[5],f[5-3]+4),即max(6,8)=8,即f[5]=8;
    j=4:f[4]=max(f[4],f[4-3]+4),即max(6,6)=6,即f[4]=6;
    j=3:f[3]=max(f[3],f[3-3]+4),即max(6,4)=6,即f[3]=6;
    当进入循环 i == 3 时: w[i] = 5; v[i] = 4;
    j=5:f[5]=max(f[5],f[5-4]+5),即max(8,7)=8,即f[5]=8;
    j=4:f[4]=max(f[4],f[4-4]+5),即max(6,5)=6,即f[4]=6;

模拟结束,最后max=8:发现没有错误,即逆序就可以解决这个优化的问题了

最后为什么一维情况下枚举背包容量需要逆序?

在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。

简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

现在是2022.3.26.1.21,拖延症太严重了,不想熬夜。。。。

到此这篇关于C语言数学问题与简单DP背包问题详解的文章就介绍到这了,更多相关C语言 数学与DP问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言使用DP动态规划思想解最大K乘积与乘积最大问题

    最大K乘积问题 设I是一个n位十进制整数.如果将I划分为k段,则可得到k个整数.这k个整数的乘积称为I的一个k乘积.试设计一个算法,对于给定的I和k,求出I的最大k乘积. 编程任务: 对于给定的I 和k,编程计算I 的最大k 乘积. 需求输入: 输入的第1 行中有2个正整数n和k.正整数n是序列的长度:正整数k是分割的段数.接下来的一行中是一个n位十进制整数.(n<=10) 需求输出: 计算出的最大k乘积. 解题思路:DP 设w(h,k) 表示: 从第1位到第K位所组成的十进制数,设m(i,j)

  • C语言数学公式来实现土味表白

    目录 前言: 一.首先简介一下所需要用到的知识点以及各个知识点的用法 二.最后我们来上程序 总结 前言:        在学习了一些编程基础以后,尤其学习了scanf函数以后,我觉得scanf函数还挺有意思的.所以就想着通过所学习的一些简单C语言基础知识,尝试着去写出一个有意思的程序.于是我就开始想有哪些有意思的数学公式,忽然就想到初中的时候玩过的一个小游戏,就是将一个任意数字放入公式中计算,结果都会是固定值,也就是5201314.所以我就去百度搜索到了这个公式.于是就写出了这个程序. (PS:

  • C语言实现数学表达式运算

    本文实例为大家分享了C语言实现数学表达式运算的具体代码,供大家参考,具体内容如下 1.开发思路: (假设有表达式 2 * 3 * ( 1 + 2) ) 数字要一个一个取出放在内存中,根据相邻前后2个计算符号,判断是否要取出数字进行计算,2个数字的计算值重新放在内存中且顺序放置.考虑使用栈这种数据结构去保存数字和符号,用2个栈,1个栈保存数字,一个栈保存运算符号. 2.因要使用栈这种数据结构,本代码使用纯C语言开发,故先编写栈的代码,参考: c语言实现通用数据结构(三):通用椎栈 3.重要处理逻辑

  • C语言数学问题与简单DP背包问题详解

    目录 数学 买不到的数目 蚂蚁感冒 饮料换购 简单DP 背包问题 二维 一维 数学 顾名思义,数学类的题就是都可以用数学知识求解. 买不到的数目 这是第四届蓝桥杯省赛C++A组,第四届蓝桥杯省赛JAVAC组的一道题 小明开了一家糖果店. 他别出心裁:把水果糖包成4颗一包和7颗一包的两种. 糖果不能拆包卖. 小朋友来买糖的时候,他就用这两种包装来组合. 当然有些糖果数目是无法组合出来的,比如要买 10 颗糖. 你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17. 大于17的任何数字

  • C语言动态规划之背包问题详解

    01背包问题 给定n种物品,和一个容量为C的背包,物品i的重量是w[i],其价值为v[i].问如何选择装入背包的物品,使得装入背包中的总价值最大?(面对每个武平,只能有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入物品多次) 声明一个数组f[n][c]的二维数组,f[i][j]表示在面对第i件物品,且背包容量为j时所能获得的最大价值. 根据题目要求进行打表查找相关的边界和规律 根据打表列写相关的状态转移方程 用程序实现状态转移方程 真题演练: 一个旅行者有一个最多能装M公斤的背

  • 基于Go和PHP语言实现爬楼梯算法的思路详解

    爬楼梯(Climbing-Stairs) 题干: 假设你正在爬楼梯.需要 n 阶你才能到达楼顶.每次你可以爬 1 或 2 个台阶.你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数.示例 1:  输入: 2  输出: 2  解释: 有两种方法可以爬到楼顶.  1. 1 阶 + 1 阶  2. 2 阶示例 2:  输入: 3  输出: 3  解释: 有三种方法可以爬到楼顶.  1. 1 阶 + 1 阶 + 1 阶  2. 1 阶 + 2 阶  3. 2 阶 + 1 阶来源:力扣 这题

  • C语言中枚举与指针的实例详解

     C语言中枚举与指针的实例详解 总结一下, 定义枚举,用typedef enum关键字, 比如 typedef enum{Red,Green,Blue} Color3; 枚举到数值的转换,如果没有指定代表数值就是从0开始算, 比如 Color3 c=Red; printf("%d",c);会显示0, 除非指定 如typedef enum{Red=3,Green=5,Blue=10} Color3; 关于类型指针的定义, 定义的时候在变量名左边加*代表此变量只是一个空指针而已, 若需要赋

  • C语言之复杂链表的复制方法(图示详解)

    什么是复杂链表? 复杂链表指的是一个链表有若干个结点,每个结点有一个数据域用于存放数据,还有两个指针域,其中一个指向下一个节点,还有一个随机指向当前复杂链表中的任意一个节点或者是一个空结点.今天我们要实现的就是对这样一个复杂链表复制产生一个新的复杂链表. 复杂链表的数据结构如下: typedef int DataType; //数据域的类型 //复杂链表的数据结构 typedef struct ComplexNode { DataType _data ; // 数据 struct Complex

  • Java语言实现快速幂取模算法详解

    快速幂取模算法的引入是从大数的小数取模的朴素算法的局限性所提出的,在朴素的方法中我们计算一个数比如5^1003%31是非常消耗我们的计算资源的,在整个计算过程中最麻烦的就是我们的5^1003这个过程 缺点1:在我们在之后计算指数的过程中,计算的数字不都拿得增大,非常的占用我们的计算资源(主要是时间,还有空间) 缺点2:我们计算的中间过程数字大的恐怖,我们现有的计算机是没有办法记录这么长的数据的,所以说我们必须要想一个更加高效的方法来解决这个问题 当我们计算AB%C的时候,最便捷的方法就是调用Ma

  • C语言实现的一个三子棋游戏详解流程

    目录 前言 一.三子棋完成程序运行结果 二.三子棋代码实现 1.创建源文件与头文件 2.整体页面的制作 3.制作并打印棋盘 1.在test.c文件中,定义函数game(); 2.在game.h 头文件中 3.在game.c源文件中 4.人机互动下棋 1.在test.c源文件中 2.在game.h头文件中 3.在game.c源文件中 4.此时打印效果 5.判断输赢 1.在test.c源文件中 2.在game.h头文件中 3.在game.c源文件中 4.最终实现结果 最后 前言 三子棋是我们先前所学

  • R语言学习ggplot2绘制统计图形包全面详解

    目录 一.序 二.ggplot2是什么? 三.ggplot2能画出什么样的图? 四.组装机器 五.设计图纸 六.机器的零件 1. 零件--散点图 1) 变换颜色 2) 拟合曲线 3) 变换大小 4) 修改透明度 5) 分层 6) 改中文 2. 零件--直方图与条形图 1) 直方图 2) 润色 3) 条形图 3. 零件--饼图 4. 零件--箱线图 5. 零件--小提琴图 6. 零件打磨 7. 超级变变变 8. 其他常用零件 七.实践出真知 八.学习资源 九.参考资料 一.序 作为一枚统计专业的学

  • C语言 小游戏打砖块实现流程详解

    始祖是美国英宝格公司(en:Atari Games,ja:アタリ (ゲーム))于1976年推出的街机游戏"Breakout"(en:Breakout),由该公司在1972年发行的"PONG"(en:PONG,ja:ポン (ゲーム),世界上第一款电子游戏,类似台球)改良而来.相较于其前作,一个人就可以玩与变化丰富这两项特点让Breakout相当卖座,使各家公司竞相模仿. 因为规则简单与游戏性,现在许多移动电话都有内建打砖块游戏,也有许多因特网小游戏版本,目前在网上可以

  • C语言实现单链表的基本功能详解

    1.首先简单了解一下链表的概念: 要注意的是链表是一个结构体实现的一种线性表,它只能从前往后,不可以从后往前(因为next只保存下一个节点的地址).在实现单链表的操作时,需要用指针来操作.很简单,注释写的很详细,欢迎大家指正哈哈哈哈~之前写的太烂了重新写了一下..... 2.代码展示: #include <stdio.h> #include <assert.h> #include <stdlib.h> typedef struct linklist { int data

随机推荐