一维dp知识点

1.一维DP的核心:

用一维数组 dp[i] 记录状态,通过清晰的递推关系(状态转移)求解。

2. 基础模型:线性递推

核心是找到 dp[i]dp[i-1]dp[i-2] 的关系。

  • 爬楼梯dp[i] = dp[i-1] + dp[i-2]

  • 最小花费爬楼梯dp[i] = min(dp[i-1], dp[i-2]) + cost[i]

3. 经典线性DP

这类问题通常需要在数组或序列上进行决策。

  • 最大子段和dp[i] = max(nums[i], dp[i-1] + nums[i])

  • 最长上升子序列 (LIS) :这是重中之重,其定义状态的方式(以i结尾) 和**转移思想(向前查找)**是解决很多难题的关键 。

4. 一维DP优化技巧

  • 滚动数组 :当 dp[i] 只依赖于前几个状态时,可以用几个变量代替整个数组,将空间复杂度从 O(n) 降到 O(1) 。

  • 二维转一维(背包问题核心) :这是最难也是最关键的部分。以 01背包 为例,核心优化是一维数组,必须逆序遍历背包容量,以确保每个物品只被选一次。这个思想会贯穿所有背包问题 。

模型:

背包类型 特点 核心代码(一维优化版)
01背包 每种物品只能选0或1个 for (int j=m; j>=v[i]; j--) dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
完全背包 每种物品可以选无限个 for (int j=v[i]; j<=m; j++) dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
多重背包 每种物品有数量限制 使用二进制优化,将问题分解为01背包

01背包:

cpp 复制代码
int dp[maxV] = {0};
for (int i = 1; i <= n; i++) {          // 遍历物品
    int v, w; cin >> v >> w;            // 体积,价值
    for (int j = m; j >= v; j--) {      // 容量逆序
        dp[j] = max(dp[j], dp[j - v] + w);
    }
}
cout << dp[m] << endl;

完全背包:

cpp 复制代码
int dp[maxV] = {0};
for (int i = 1; i <= n; i++) {
    int v, w; cin >> v >> w;
    for (int j = v; j <= m; j++) {      // 容量正序
        dp[j] = max(dp[j], dp[j - v] + w);
    }
}
cout << dp[m] << endl;

多重背包:

cpp 复制代码
int dp[maxV] = {0};
for (int i = 1; i <= n; i++) {
    int v, w, s; cin >> v >> w >> s;    // 体积,价值,数量
    // 二进制拆分
    for (int k = 1; k <= s; k <<= 1) {
        int val = k * v, wei = k * w;
        for (int j = m; j >= val; j--) {   // 01背包逆序
            dp[j] = max(dp[j], dp[j - val] + wei);
        }
        s -= k;
    }
    if (s > 0) {                         // 剩余部分
        int val = s * v, wei = s * w;
        for (int j = m; j >= val; j--) {
            dp[j] = max(dp[j], dp[j - val] + wei);
        }
    }
}
cout << dp[m] << endl;

例题:

装箱问题

题目描述

有一个箱子容量为 V(正整数,0≤V≤20000),同时有0≤V≤20000),同时有n个物品(个物品(0 \leq n \leq 30$),每个物品有一个体积(正整数)。

要求 nn 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入描述

输入第一行,一个整数,表示箱子容量。

第二行,一个整数 nn,表示有 nn 个物品。

接下来 nn 行,分别表示这 nn 个物品的各自体积。

输出描述

输出一行,表示箱子剩余空间。

输入输出样例

示例 1

输入

复制代码
24
6
8
3
12
7
9
7

输出

复制代码
0
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int dp[30000];
int main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  int t,m;
  cin>>t>>m;
  for(int i=1;i<=m;i++)
  {
    int shj;
    cin>>shj;
    for(int j=t;j>=shj;j--)
    {
      dp[j]=max(dp[j],dp[j-shj]+shj);//dp要表示体积,将+价值改为+shj即可
    }
  }
    cout<<t-dp[t];
    return 0;
}

你有一架天平和 NN 个砝码,这 NN 个砝码重量依次是 W1,W2,⋅⋅⋅,WNW1​,W2​,⋅⋅⋅,WN​。

请你计算一共可以称出多少种不同的重量? 注意砝码可以放在天平两边。

输入格式

输入的第一行包含一个整数 NN。

第二行包含 NN 个整数:W1,W2,W3,⋅⋅⋅,WNW1​,W2​,W3​,⋅⋅⋅,WN​。

输出格式

输出一个整数代表答案。

样例输入

复制代码
3
1 4 6

样例输出

复制代码
10

样例说明

能称出的 1010 种重量是:1、2、3、4、5、6、7、9、10、111、2、3、4、5、6、7、9、10、11​。

1=1;1=1;

2=6−4(2=6−4(天平一边放 66,另一边放 4);4);​

3=4−1;3=4−1;

4=4;4=4;

5=6−1;5=6−1;​

6=6;6=6;

7=1+6;7=1+6;

9=4+6−1;9=4+6−1;

10=4+6;10=4+6;

11=1+4+6。11=1+4+6。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll N;
ll a[200];
ll summ=0;
ll ans=0;
int dp[200][200000];
//dp[i][j]表示用到前i个砝码,能否称出j重量
//1为可以,0为不可以

int main()
{
  // 请在此输入您的代码
  cin>>N;
  for(int i=1;i<=N;i++){
    cin>>a[i];
    summ+=a[i];
  }

  for(int i=1;i<=N;i++){
    for(int j=1;j<=summ;j++){//遍历所有可能的重量
      dp[i][j]=dp[i-1][j];//继承前一个状态
      if(dp[i][j]==0){//如果普通继承下来,发现这个不行呢?
        if(j==a[i]) dp[i][j]=1;//如果需要的重量正好就是第i个砝码,那么可以
        if(dp[i-1][j+a[i]]==1) dp[i][j]=1;//如果前i-1个能搞出j+a[i]重量,那么把第i个砝码放到另一侧就行
        if(dp[i-1][abs(j-a[i])]==1) dp[i][j]=1;//如果前i-1个砝码能搞出abs(j-a[i])重量
        //那么把第i个砝码放同侧就行
      }
    }
  }

  for(int j=1;j<=summ;j++){
    if(dp[N][j]==1) ans++;//遍历,看dp[][]==1的个数,就是答案
  }
  cout<<ans<<endl;
  return 0;
}

小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有

𝑁

种蒸笼,其中第

𝑖

种蒸笼恰好能放

𝐴

𝑖

个包子。每种蒸笼都有非常多笼,可以认为是无限笼。

每当有顾客想买

𝑋

个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有

𝑋

个包子。比如一共有 3 种蒸笼,分别能放 3、4 和 5 个包子。当顾客想买 11 个包子时,大叔就会选 2 笼 3 个的再加 1 笼 5 个的(也可能选出 1 笼 3 个的再加 2 笼 4 个的)。

当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有 3 种蒸笼,分别能放 4、5 和 6 个包子。而顾客想买 7 个包子时,大叔就凑不出来了。

小明想知道一共有多少种数目是包子大叔凑不出来的。

输入描述

第一行包含一个整数

𝑁

(

1

𝑁

100

)。

以下 N 行每行包含一个整数

𝐴

𝑖

(

1

𝐴

𝑖

100

)。

输出描述

一个整数代表答案。如果凑不出的数目有无限多个,输出 INF。

输入输出样例

示例 1

输入

2

4

5

输出

6

样例说明

凑不出的数目包括:1, 2, 3, 6, 7, 11。

示例 2

输入

2

4

6

输出

INF

样例说明

所有奇数都凑不出来,所以有无限多个

cpp 复制代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e7;
int num[105];
int dp[N];
int main()
{
  // 请在此输入您的代码
  int n;
  int des = 0;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
  {
    scanf("%d",&num[i]);
    dp[num[i]]++;
  }
  for(int i=1;i<=N;i++)
  {
    for(int j=1;j<=n;j++)
    {
      if(i-num[j]<0)
      {
        continue;
      }
      dp[i]=dp[i-num[j]]+dp[i];
      if(dp[i]!=0)
      {
        break;
      }
    }
    if(dp[i]==0)
    {
        des++;
    }
  }
  if(des>10000)
  {
      printf("INF");
      return 0;
  }
  printf("%d",des);
  return 0;
}

题目描述

小明开了一家糖果店。他别出心裁:把水果糖包成 4 颗一包和 7 颗一包的两种。糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是 17。大于 17 的任何数字都可以用 4 和 7 组合出来。

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

输入描述

输入两个正整数,表示每种包装中糖的颗数(都不多于 1000 )。

输出描述

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

不需要考虑无解的情况

输入输出样例

示例

输入

复制代码
4 7

输出

复制代码
17
cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;
int dp[10000005];
int arr[3];
int maxx = 0;
int main()
{
    cin >> arr[1] >> arr[2];
    dp[arr[1]] = 1;
    dp[arr[2]] = 1;
    for (int i = 1;i < 3;i++)
    {
        for (int j = arr[i];j < 10000000;j++)
        {
            if (dp[j]) 
            {
                dp[j + arr[i]] = 1;
            } 
            if (dp[j]==0) maxx= j;
        }
    }
    cout << maxx << endl;
    return 0;
}

题目描述

小明有一个容量为 VV 的背包。

这天他去商场购物,商场一共有 NN 种物品,第 ii 种物品的体积为 wiwi​,价值为 vivi​,数量为 sisi​。

小明想知道在购买的物品总体积不超过 VV 的情况下所能获得的最大价值为多少,请你帮他算算。

输入描述

输入第 11 行包含两个正整数 N,VN,V,表示商场物品的数量和小明的背包容量。

第 2∼N+12∼N+1 行包含 33 个正整数 w,v,sw,v,s,表示物品的体积和价值。

1≤N≤1021≤N≤102,1≤V≤2×1021≤V≤2×102,1≤wi,vi,si≤2×1021≤wi​,vi​,si​≤2×102。

输出描述

输出一行整数表示小明所能获得的最大价值。

输入输出样例

示例 1

输入

复制代码
3 30
1 2 3
4 5 6
7 8 9

输出

复制代码
39
cpp 复制代码
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1010;

int n, m;
int v[2*N], w[2*N], s[2*N];
int f[2*N][2*N];

int main(){
    
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i] >> s[i];
    }
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++) {
            for(int k = 0; k <= s[i] && k * v[i] <= j; k++) {
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k *w[i]);
            }
        }
    }
    cout << f[n][m];
    return 0;    
}
相关推荐
ZHANG13HAO2 小时前
蚁群算法(蚁聚算法)深度解析与 mTSP 实战:物流多车协同配送优化
人工智能·算法·机器学习
D_C_tyu2 小时前
HTML | 基于权重评估算法实现自动游戏功能的俄罗斯方块小游戏
算法·游戏·html
小肝一下2 小时前
每日两道力扣,day1
算法·leetcode·职场和发展
WBluuue2 小时前
AtCoder Beginner Contest 451(ABCDEFG)
c++·算法
im_AMBER2 小时前
Leetcode 151 最大正方形 | 买卖股票的最佳时机 III
数据结构·算法·leetcode·动态规划
Fly Wine2 小时前
Leetcode之简单题:在区间范围内统计奇数数目
算法·leetcode·职场和发展
CoderCodingNo2 小时前
【GESP】C++五级练习题 luogu-P1102 A-B 数对
开发语言·c++·算法
cpp_25012 小时前
B3873 [GESP202309 六级] 小杨买饮料
数据结构·c++·算法·动态规划·题解·洛谷
2301_789015622 小时前
C++11新增特性:可变参数模板、lambda表达式、function包装器、bind绑定、defult和delete
c语言·开发语言·c++·算法·c++11·万能引用