[AcWing Learning] Dynamic Programming

knapsack problem

DP considers two dimensions:

  • State representation : f ( i , j ) f(i, j)f(i,The meaning of j )
  • State computing : the essence is the division of sets
DP
状态表示 f(i, j)
集合
所有的选法
条件
数目限制
体积限制
属性
(max, min, 数量)
状态计算
本质就是集合的划分

01 Backpack -> Item can only be used once

Topic: 2.01 Knapsack Problem - AcWing Question Bank

have NNN items and a capacity isVVV 's backpack,each item can only be used once.
SectionIIThe volume of item i is vi v_ivi,This is wi w_iwi.
Solve which items to put into the backpack so that the total volume of these items does not exceed the capacity of the backpack and the total value is the largest.

f ( i , j ) f(i, j) f(i,j ) means from0 00 ~ i i Select i items and put them in with a capacity of jjmaximum value in j 's knapsack

f ( i , j ) = M a x ( f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i ) f(i, j) = Max( f(i-1,j), f(i-1, j-v_i) + w_i) f(i,j)=Max(f(i1,j),f(i1,jvi)+wi)

  • f ( i − 1 ) ( j ) f(i - 1)(j) f(i1 ) ( j ) : do not choose itemiiThe maximum value of a set of i items
  • f ( i − 1 ) ( j − v ( i ) ) + w ( i ) f(i-1)(j-v(i))+w(i) f(i1)(jv ( i ))+w ( i ) : choiceiiThe maximum value of the set of i items (theiiSubtract the volume of i items, and find the maximum value in the remaining set)

Two-dimensional realization:

#include <iostream>
using namespace std;
const int N = 1010;

int n, m; // 物品数量, 背包容量
int v[N]; // 体积
int w[N]; // 价值
int f[N][N]; // f[i][j], 前 i 个物品中体积不超过 j 的最大价值

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

Rolling arrays are optimized to be one-dimensional:

  • Because only the state of i - 1 is used in the calculation of i, the dimension i can be deleted directly, and the rolling array is used
  • The second traversal should be traversed in reverse order to prevent updating with the updated value again
#include <iostream>
using namespace std;
const int N = 1010;

int n, m; // 物品数量, 背包容量
int v[N]; // 体积
int w[N]; // 价值
int f[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[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;

    return 0;
}

Full Backpack -> Items can be used unlimited times

Topic: 3. Complete Knapsack Problem - AcWing Question Bank

There are N items and a knapsack of capacity V, and infinite pieces of each item are available .
Section IIThe volume of i items isvi v_ivi,This is wi w_iwi.
Solve which items to put into the backpack so that the total volume of these items does not exceed the capacity of the backpack and the total value is the largest.

f ( i ) ( j ) f(i)(j) f ( i ) ( j ) means from0 00 ~ i i Select i items and put them in with a capacity of jjmaximum value in j 's knapsack

Simple approach:

#include <iostream>
using namespace std;
const int N = 1010;

int n, m; // 物品数量, 背包容量
int v[N], w[N]; // 体积, 价值
int f[N][N]; // f[i][j], 前 i 个物品中体积不超过 j 的最大价值

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 = 1; j <= m; j++) // 背包
            for (int k = 0; k * v[i] <= j; k++) // 完全背包不限物品放入次数, 遍历物品的个数
                f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k]  + w[i] * k);

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

    return 0;
}

Simple approach optimization:

f[i , j ] =   max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]=   max(            f[i-1,j-v]   ,  f[i-1,j-2*v]+w   , f[i-1,j-3*v]+2*w , .....)
由上式,可得出如下递推关系: f[i][j] = max(f[i, j-v] + w, f[i-1][j]) 
#include <iostream>
using namespace std;
const int N = 1010;

int n, m; // 物品数量, 背包容量
int v[N], w[N]; // 体积, 价值
int f[N][N]; // f[i][j], 前 i 个物品中体积不超过 j 的最大价值

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 = 1; j <= m; j++) // 背包
            if (j < v[i]) f[i][j] = f[i - 1][j];
            else f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);

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

    return 0;
}

Scrolling array optimization:

#include <iostream>
using namespace std;
const int N = 1010;

int n, m; // 物品数量, 背包容量
int v[N], w[N]; // 体积, 价值
int f[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 = v[i]; j <= m; j++) // *
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;

    return 0;
}

01 Backpack and full backpack

01 The difference between the final wording of the backpack and the complete backpack is only in the traversal order of the inner loop.

01 Final writing of the backpack:

int n, m; // 物品数量, 背包容量
int v[N], w[N]; // 体积, 价值
int f[N];

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]);

The final way of writing the complete backpack:

int n, m; // 物品数量, 背包容量
int v[N], w[N]; // 体积, 价值
int f[N];

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]);

Multiple Backpacks -> Items can be used a specified number of times

Topic: 4. Multiple Knapsack Problem I - AcWing Question Bank

have NNN items and a capacity isVVV 's backpack.
SectionIIi items have at mostsi s_isipieces, the volume of each piece is vi v_ivi,This is wi w_iwi.
Solve which items to put into the backpack, so that the sum of the volume of the items does not exceed the capacity of the backpack, and the sum of the values ​​is the largest.

#include <iostream>
using namespace std;
const int N = 1010;

int n, m; // 物品数量, 背包体积
int v[N], w[N], s[N]; // 体积, 价值, 数量
int f[N][N]; // f[i][j], 前 i 个物品中体积不超过 j 的最大价值

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 = 1; 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 - v[i] * k] + w[i] * k);

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

    return 0;
} 

Linear DP

number triangle

Topic: 898. Numeral Triangle - AcWing Question Bank
LeetCode: 120. Minimal Path Sum of Triangle - LeetCode

Given a digital triangle as shown in the figure below, starting from the top, at each node, you can choose to move to the node at the bottom left or to the node at the bottom right, and go all the way to the bottom, and you are required to find a path , to maximize the sum of the numbers on the path.

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

Recursively from top to bottom:

#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, INF = 1e9;

int n; 
int a[N][N];
int dp[N][N]; // dp[i][j] 从起点到 (i, j) 路径的最大值

int main() {
    
    
    cin >> n;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            cin >> a[i][j];
            
    // 初始化 DP 数组的值为负无穷
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= i + 1; j++)
            dp[i][j] = -INF;

    dp[1][1] = a[1][1]; // 初始状态
    for (int i = 2; i <= n; i++)
        for (int j = 1; j <= i; j++)
            dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + a[i][j];

    // 找出最后一行的最大值
    int res = -INF;
    for (int i = 1; i <= n; i++) res = max(res, dp[n][i]);   
    cout << res << endl;

    return 0;
}

Recursively from bottom to top: store the original array with dpan array

#include<bits/stdc++.h>
using namespace std;
const int N = 510;

int dp[N][N];
int n;

int main() {
    
    
    cin >> n;
    
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            cin >> dp[i][j];

    for (int i = n; i >= 1; i--)
        for (int j = i; j >= 1; j--)
            dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + dp[i][j];

    cout << dp[1][1] << endl;
    
    return 0;
}

longest ascending subsequence

Title: 895. Longest Ascending Subsequence - AcWing Question Bank
LeetCode: 300. Longest Ascending Subsequence - LeetCode

Given a length NNFor a sequence of N , find the longest length of a subsequence whose values ​​are strictly monotonically increasing.

#include <iostream>
using namespace std;
const int N = 1010;

int n;
int a[N];
int dp[N]; // f[i] 以 i 结尾的上升子序列的长度

int main()
{
    
    
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    int res = 1;
    for (int i = 1; i <= n; i++)
    {
    
    
        dp[i] = 1; // 只有 a[i] 一个数
        for (int j = i - 1; j >= 1; j--)
            if (a[j] < a[i])
                dp[i] = max(dp[i], dp[j] + 1);
        res = max(res, dp[i]);
    }
    cout << res << endl;

    return 0;
}

longest common subsequence

Title: 897. Longest Common Subsequence - AcWing Question Bank
LeetCode: 1143. Longest Common Subsequence - LeetCode

Given two lengths NNN andMMM stringAAA andBBB , seeking to beAAA subsequence isBBWhat is the longest string length of the subsequence of B.

The problem of two string subsequences can be considered from the perspective of two-dimensional dp.

#include <iostream>
using namespace std;
const int N = 1010;

int n, m;
char a[N], b[N]; 
int f[N][N]; // 由 a 前 i 个元素, b 前 j 个元素构成的最长子序列的长度

int main()
{
    
    
    cin >> n >> m;
    scanf("%s%s", a + 1, b + 1); // 多读入一个长度的字符串

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

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

    return 0;
}

shortest edit distance

Topic: 902. Shortest Edit Distance - AcWing Question Bank
LeetCode: 72. Edit Distance - LeetCode

Given two strings AAA andBBB , now want to transferAAA becomes BBafter several operationsB , the operations that can be performed are:

  1. delete – replace the string AAA character in A is deleted.
  2. insert – in the string AAInsert a character at a certain position in A.
  3. Replace – replace the string AAA character in A is replaced by another character.
    Now please find out, willAAA becomesBBAt least how many operations B needs to perform.

1. Delete a character at the end of a to make the two strings equal f[i][j] = f[i - 1][j] + 1
2. Add a character at the end of a to make the two strings equal f[i][j] = f[i][j - 1] + 1
3. Replace a character at the end of a to make the two strings equal f[i][j] = f[i - 1][j - 1] + 1
4. Do not operate on the last character of a (indicate the last character of a and b One character is already equal), the other characters of operation a make the two strings equalf[i][j] = f[i - 1][j - 1]

#include <iostream>
using namespace std;
const int N = 1010;

int n, m;
char a[N], b[N];
int f[N][N]; // f[i][j] a 的前 i 个字母转换成 b 的前 j 个字母使用的最少操作数

int main()
{
    
    
    scanf("%d%s", &n, a + 1);
    scanf("%d%s", &m, b + 1);

    // 初始化 dp 数组
    // 只能使用增加操作
    for (int i = 0; i <= m; i++) f[0][i] = i;
    // 只能使用删除操作
    for (int i = 0; i <= n; i++) f[i][0] = i;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
    
    
	        // 不需要进行操作
			if (a[i] == b[j]) f[i][j] = f[i - 1][j - 1];
			// 取 删除, 新增, 修改 的最小值
			else f[i][j] = min(min(f[i - 1][j], f[i][j - 1]), f[i - 1][j - 1]) + 1;
        }

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

    return 0;
}

edit distance

Topic: 899. Edit Distance - AcWing Question Bank

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;

int n, m;
char str[N][N]; // 存储给定的 n 个字符串
int f[N][N]; // a[0..i] 转化到 b[0..j] 的最少操作次数

int minDistance(char a[], char b[])
{
    
    
    int la = strlen(a + 1), lb = strlen(b + 1);
    for (int i = 0; i <= la; i++) f[i][0] = i;
    for (int i = 0; i <= lb; i++) f[0][i] = i;
    
    for (int i = 1; i <= la; i++)
        for (int j = 1; j <= lb; j++)
        {
    
    
            if (a[i] == b[j]) f[i][j] = f[i - 1][j - 1];
            else f[i][j] = min(min(f[i - 1][j], f[i][j - 1]), f[i - 1][j - 1]) + 1;
        }

    return f[la][lb];
}

int main()
{
    
    
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> (str[i] + 1);

    while (m--) {
    
    
        int res = 0;
        char s[N];
        int limit;
        cin >> (s + 1) >> limit;
        for (int i = 0; i < n; i++)
            if (minDistance(str[i], s) <= limit) res++;
        cout << res << endl;
    }

    return 0;
}

Interval DP

Interval dp problem enumeration

  • The first dimension is usually the length of the enumeration interval, and is len = 1generally used for initialization, and the enumeration len = 2starts from
  • The starting point of the second dimension enumeration i(the right endpoint is obtained jautomatically j = i + len - 1)

Common templates for interval DP:

for (int len = 1; len <= n; len++) {
    
             // 区间长度
    for (int i = 1; i + len - 1 <= n; i++) {
    
     // 枚举起点
        int j = i + len - 1;                 // 区间终点
        if (len == 1) {
    
    
            dp[i][j] = 初始值
            continue;
        }

        for (int k = i; k < j; k++) {
    
            // 枚举分割点,构造状态转移方程
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j]);
        }
    }
}

stones merge

Title: 282. Combining Stones - AcWing Question Bank

There are N piles of stones arranged in a row, and their numbers are 1, 2, 3, ..., N.
Each pile of stones has a certain quality, which can be described by an integer. Now these N piles of stones should be merged into one pile.
Only two adjacent piles can be merged each time. The cost of merging is the sum of the masses of the two piles of stones. After merging, the stones adjacent to the two piles of stones will be adjacent to the new pile. Due to the different order of selection when merging, The total cost of the merger is also different.
The problem is: find a reasonable method to minimize the total cost and output the minimum cost.

DP
状态表示 f(i,j)
状态计算
集合:所有将 [i, j] 合并成一维的方案的集合
属性:Min
#include <iostream>
#include <cstring>
using namespace std;
const int N = 310;

int n;
int s[N]; // 前缀和
int f[N][N]; // f[i][j] 将第 i 堆石子和第 j 堆石子合并的代价的最小值

int main()
{
    
    
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> s[i];

    // 处理前缀和
    for (int i = 1; i <= n; i++) s[i] += s[i - 1];

    for (int len = 2; len <= n; len++) // 第一维: 区间长度
    {
    
    
        for (int l = 1; l + len - 1 <= n; l++) // 第二维: 区间起始点
        {
    
    
            int r = l + len - 1; // 区间终点
            f[l][r] = 0x3f3f3f;
            for (int k = l; k < r; k++) // 决策: 枚举分割点
                f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
        }
    }

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

    return 0;
}

Counting DP

integer division

Topic: 900. Integer Division - AcWing Question Bank

Complete Backpack:

#include <iostream>
using namespace std;
const int N = 1010, M = 1e9 + 7;

int n;
int dp[N]; // dp[i] 表示放满容量为 i 的背包的方案数

int main()
{
    
    
    cin >> n;
    dp[0] = 1;
    for (int i = 1; i <= n; i++) // 物品
        for (int j = i; j <= n; j++) // 背包
            dp[j] = (dp[j] + dp[j - i]) % M;
            
    cout << dp[n] << endl;
    return 0;
}

Another way of thinking: f(i, j) represents the number of solutions whose sum is i and the total number is j

This is a bit difficult to understand. . .

#include <iostream>
using namespace std;
const int N = 1010, M = (1e9 + 7);

int n;
int f[N][N]; // 总和为 i, 总个数为 j 的方案数

int main()
{
    
    
    cin >> n;
    f[1][1] = 1;
    for (int i = 2; i <= n; i++) // 总和
        for (int j = 1; j <= i; j++) // 个数
	        // 最小值是 1: f[i - 1][j - 1]
	        // 最小值 > 1, 相当于给每个数 - 1:  f[i - j][j]
            f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % M;

    int res = 0;
    for (int i = 1; i <= n; i++) res = (res + f[n][i]) % M;
    cout << res << endl;

    return 0;
}

Digital Statistics DP (TODO)

The difficulty is high, interviews are not common, skip...

State Compression DP (TODO)

The difficulty is high, interviews are not common, skip...

Tree DP

Prom Without a Boss

Title: 285. A Prom Without a Boss - AcWing Question Bank
337. Robbery III - LeetCode

#include<cstring>
#include <iostream>
using namespace std;
const int N = 6010;

int n;
int happy[N]; // 职工的高兴度
int h[N], e[N], ne[N], idx;
// f[u][0] 所有以 u 为根的子树中选择,并且不选 u 这个点的方案
// f[i][1] 所有以 u 为根的子树中选择,并且选 u 这个点的方案
int f[N][2];
bool has_father[N]; // 判断当前节点是否有父节点

void add(int a, int b)
{
    
    
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u)
{
    
    
    f[u][1] = happy[u]; // 选择当前 u 节点
    for (int i = h[u]; i != -1; i = ne[i]) // 遍历树的子节点
    {
    
     
        int j = e[i]; // 子节点
        dfs(j);
        // 不选 u, 子节点 选 或 不选
        f[u][0] += max(f[j][1], f[j][0]);
        // 选 u, 子节点 只能不选
        f[u][1] += f[j][0];
    }
}

int main()
{
    
    
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> happy[i];
    memset(h, -1, sizeof h);

    // 建树
    for (int i = 1; i < n; i++)
    {
    
    
        int a, b;
        cin >> a >> b;
        has_father[a] = true;
        add(b, a);
    }


    int root = 1;
    while (has_father[root]) root++; // 寻找根节点

    dfs(root);
    // 不选根节点 或 选根节点
    cout << max(f[root][0], f[root][1]) << endl;

    return 0;
}

memory search

ski

Topic: 901. Skiing - AcWing Question Bank

#include <cstring>
#include <iostream>
using namespace std;
const int N = 310;

int n, m; // 网格行列
int f[N][N]; // 从 h[i][j] 位置开始滑的路径的最大值
int h[N][N];
// 方向向量: 左上右下
int dx[4] = {
    
     -1, 0, 1, 0 }, dy[4] = {
    
     0, 1, 0, -1 };

int dp(int x, int y)
{
    
    
    int& v = f[x][y];
    if (v != 0) return v; // 已经计算过则直接返回答案
    v = 1;
    for (int i = 0; i < 4; i++)
    {
    
    
        // 下一个位置
        int xx = x + dx[i], yy = y + dy[i];
        // 必须在边界范围内
        if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && h[xx][yy] < h[x][y])
            v = max(v, dp(xx, yy) + 1); // 更新
    }
    return v;
}

int main()
{
    
    
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> h[i][j];

    int res = 0;
    // 可以从任意点开始
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            res = max(res, dp(i, j));
    cout << res << endl;
    return 0;
}

Guess you like

Origin blog.csdn.net/weixin_43734095/article/details/126809374