算法笔记之DP实战(二)

最大最小值DP

Choose minimum (maximum) path among all possible paths before the current state, then add value for the current state.
routes[i] = min(routes[i-1], routes[i-2], ... , routes[i-k]) + cost[i] 有时候也会min(f[x-1]+1, f[x]), 特别时重复走过x

746. Min Cost Climbing Stairs

到i的路径来自于i-1和i-2。最后一步可以为n-1或者n的总cost。

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int ss = cost.size();
        vector <int> memo (ss+1, 0x6fffffff);
        memo[0] = 0; memo[1] = cost[0];
        for (int i=2; i<=ss; i++) {
            memo[i] = min(memo[i-1], memo[i-2]) + cost[i-1]; 
        }        
        return min(memo[ss-1], memo[ss]);
    }
};

64. Minimum Path Sum

Note: You can only move either down or right at any point in time.
f[n][m] = min(f[n-1][m], f[n][m-1]) + f[n][m];

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<int>> memo(n+1, vector<int>(m+1));
        // 給memo[0][1], memo[1][0]赋初值0
        for (int i=2; i<=n; i++) {
            memo[i][0] = 0x6fffffff;
        }
        for (int i=2; i<=m; i++) {
            memo[0][i] = 0x6fffffff;
        }
        
        for (int i=1; i<=n; i++) {
            for (int j=0; j<=m; j++) {
              
                memo[i][j] = min(memo[i-1][j], memo[i][j-1]) + grid[i-1][j-1];
            }
        }
        return memo[n][m];
    }
};

改良版->不需要memo, 用if处理padding,

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        // f[n][m] = min(f[n-1][m], f[n][m-1]) + f[n][m];
        
        for (int i=0; i<n; i++) {
            for (int j=0; j<m; j++) {
                if (j>0 && i>0) 
                    grid[i][j] += min(grid[i-1][j], grid[i][j-1]);
                else if (i>0)
                    grid[i][j] += grid[i-1][j];
                else if (j>0)
                    grid[i][j] += grid[i][j-1];
            }
        }

        return grid[n-1][m-1];
    }
};

322. Coin Change

f[n] = min(f[n-coins[i]], f[n-coins[i+1]], ....) + 1; memo[0] = 0; 如果memo[amount]是0x6fffffff,则表示没走到这过(没发生过更新)返回-1。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int ss = coins.size();
        vector<int> memo(amount+1, 0x6fffffff);
        memo[0] = 0;
        for (int i=1; i<=amount; i++) {
            for (int j=0; j<ss;  j++) {
                if (coins[j]>i) continue;
                memo[i] = min(memo[i-coins[j]] + 1, memo[i]);
            }
        }
        return memo[amount] != 0x6fffffff? memo[amount] : -1;
    }
};

931. Minimum Falling Path Sum

类似 Minimum Path Sum,
f[x][y] = min(f[x-1][y], f[x-1][y-1], f[x-1][y+1]) + f[x][y], 可直接在矩阵上操作, 然后判断边缘条件决定转移方程(e.g. y==0时候,忽略f[x-1][y-1])。

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& A) {
        
        int n = A.size(), m = A[0].size();
        for (int x=1; x<n; x++) {
            for (int y=0; y<m; y++) {
                if (y && m-1-y) 
                    A[x][y] += min(A[x-1][y-1], min(A[x-1][y], A[x-1][y+1]));
                else if (y)
                    A[x][y] += min(A[x-1][y], A[x-1][y-1]);
                else if (m-1-y)
                    A[x][y] += min(A[x-1][y], A[x-1][y+1]);
                else 
                    A[x][y] += A[x-1][y];
            }
        }

        return *min_element(A[n-1].begin(), A[n-1].end());
    }
};

!983. Minimum Cost For Tickets Medium

f[day]相当于当日若出行,最低总票价
f[day] = min(f[day-1]+ticket_1, f[day-7]+ticket_2, f[day-30]+ticket_2); if f[day] not in days -> f[day] = f[day-1];

class Solution {
public:
    int mincostTickets(vector<int>& days, vector<int>& costs) {

        int max_day = days[days.size()-1];
        vector <int> memo(max_day+1);
        
        int idx = 0; // memo[0] = *min_element(costs.begin(), costs.end());
        for (int i=1; i<=max_day; i++) {
            if (i<days[idx]) memo[i] = memo[i-1];
            else {
                if (i>29)
                    memo[i] = min(min(memo[i-1]+costs[0], 
                                      memo[i-7]+costs[1]), 
                                      memo[i-30]+costs[2]);
                else if (i>6) 
                    memo[i] = min(min(memo[i-1]+costs[0], 
                                      memo[i-7]+costs[1]),
                                      costs[2]);
                else 
                    memo[i] = min(min(memo[i-1]+costs[0], 
                                      costs[1]),
                                      costs[2]);
                
                idx ++;
            }
        }
        
        return memo[max_day];
    }
};

!650. 2 Keys Keyboard Medium

更新都是来自公约数,所以对2n和1n/2循环
dp[n] = min(dp[n], dp[n/cnt] + n/cnt + 1 - 1); AA 复制到 AAAA只要一次粘贴操作

class Solution {
public:
    int minSteps(int n) {
        if (n == 1) return 0;
        vector<int> dp(n+1, 0x6fffffff);
        dp[1] = 0, dp[2] = 2;   
        for (int i=3; i<=n; i++) {
            for (int j=i/2; j>0; j--) {
                if (i%j) continue;
                // AA -> AA AA just need to copy once!
                dp[i] = min(dp[i], dp[j] + i/j + 1 - 1);
            }
        }
        return dp[n];
    }
};

再简化版 -> f[2]也符合规律

class Solution {
public:
    int minSteps(int n) {
        vector<int> dp(n+1, 0x6fffffff);
        dp[1] = 0;   
        for (int i=2; i<=n; i++) {
            for (int j=i/2; j>0; j--) {
                if (i%j) continue;
                // AA -> AA AA just need to paste once! and copy also needs one operation. so 1-1;
                dp[i] = min(dp[i], dp[j] + i/j + 1 - 1);
            }
        }
        return dp[n];
    }
};

279. Perfect Squares Medium

f[n] = min(f[n-sqr_num[i]] + 1, f[n]);

int numSquares(int n) {
        
        if (!n) return 0;
        vector<int> dp(n+1, 0x6fffffff), sqr_nums;
        for (int i=1; i<=static_cast<int>(sqrt(n)); i++) {
            sqr_nums.push_back(pow(i, 2));
        }
        int ss = sqr_nums.size();
        dp[0]=0;
        
        for (int i = 1; i<=n; i++) {
            for (int j = 0; j < ss; j++) {
                if (sqr_nums[j]>i) break;
                dp[i] = min(dp[i-sqr_nums[j]] + 1, dp[i]);
            }
        }
        return dp[n];
    }
};

简单优化
找小于等于n的次方for (int i=1; ii<=n; i++) sqr_nums.push_back(ii);

class Solution {
public:
    int numSquares(int n) {
        // f[n] = min(f[n-sqr_num[i]] + 1, f[n]);

        vector<int> dp(n+1, 0x6fffffff), sqr_nums;
        for (int i=1; i*i<=n; i++) sqr_nums.push_back(i*i);

        int ss = sqr_nums.size();
        dp[0]=0;
        
        for (int i = 1; i<=n; i++) {
            for (int j = 0; j < ss; j++) {
                if (sqr_nums[j]>i) break;
                dp[i] = min(dp[i-sqr_nums[j]] + 1, dp[i]);
            }
        }
        return dp[n];
    }
};

120. Triangle Medium

类似931. Minimum Falling Path Sum

dp[i][j] = min(dp[i-1][j], dp[i-1][j-1], dp[i-1][j+1]) + dp[i][j], 还要处理边缘的case

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        for (int i=1; i<triangle.size(); i++) {
            int ss = triangle[i].size();
            for (int j=0; j<ss; j++) {
                if (!j) 
                    triangle[i][j] += triangle[i-1][j];
                else if (j==ss-1) 
                    triangle[i][j] += triangle[i-1][j-1];
                else 
                    triangle[i][j] += min(triangle[i-1][j], 
                                          triangle[i-1][j-1]);
            }
        }
        return *min_element(triangle[triangle.size()-1].begin(),         
                            triangle[triangle.size()-1].end());
    }
};

1049. Last Stone Weight II Medium

474. Ones and Zeroes Medium

221. Maximal Square Medium

322. Coin Change Medium

1240. Tiling a Rectangle with the Fewest Squares Hard

174. Dungeon Game Hard

871. Minimum Number of Refueling Stops Hard

最长上升子序列

虽然是O(N)的空间,但是操作还是要O(N^2)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int n;
vector<int> vec{}, dp{};

void run() {
    dp[0] = 1;
    for (int i=1; i<n; i++) {
        dp[i] = 1;
        for (int j=0; j<i; j++)
            if (vec[i] > vec[j])
                dp[i] = max(dp[i], dp[j]+1);
    } 
}

int main() {
    cin >> n;
    vec = vector<int>(n);
    dp = vector<int>(n);
    for (int i=0; i<n; i++) 
        cin >> vec[i];
    run();
    cout << *max_element(dp.begin(), dp.end());
}

用二分优化,dp[n]表示,序列长为n时,结尾元素最小为多少。因此1~n,是有序的,可以二分来操作

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int n, maxi = 1;
vector<int> vec{}, dp{};

void run() {
    dp[0] = vec[0];
    for (int i=1; i<n; i++) {
        if (vec[i] > dp[maxi-1]) dp[maxi++] = vec[i];
        else {
            int l=0, r=maxi-1;
            while (l<r) {
                int mid = (l + r) / 2;
                if (dp[mid] >= vec[i]) r = mid;
                else l = mid + 1;
            }
            // cout <<"[i]:"<<i<<", [r]:"  <<r << endl;
            dp[l] = vec[i]; // 没有比l小的情况那l为0, 只要有比他小的l就大于0
        }
        // for (auto ii:dp) cout << ii << " ";
        //     cout << endl;
    } 
}

int main() {
    cin >> n;
    vec = vector<int>(n);
    dp = vector<int>(n, -0x6fffffff);
    for (int i=0; i<n; i++) 
        cin >> vec[i];
    run();
    cout << maxi;
}

2.达到目标的方法总数dp(Distinct Ways)

Statement:Given a target find a number of distinct ways to reach the target.

Approach:Sum all possible ways to reach the current state.

routes[i] = routes[i-1] + routes[i-2], ... , + routes[i-k]

70. Climbing Stairs

class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n+1, 1);
        
        for (int i=2; i<=n; i++) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
};

62. Unique Paths

f[i][j] = f[i][j-1]+f[i-1][j];

class Solution {
public:
    int uniquePaths(int m, int n) {
        // f[i][j] = f[i][j-1]+f[i-1][j];
        vector<vector<int>> dp(m, vector<int>(n, 0));
        dp[0][0] = 1; 
        for(int i=0; i<m; i++) {
            for (int j=0; j<n; j++) {
                if (i && j)
                    dp[i][j] = dp[i][j-1] + dp[i-1][j];
                else if (i)
                    dp[i][j] = dp[i-1][j];
                else if (j)
                    dp[i][j] = dp[i][j-1];
            }
        }
        
        return dp[m-1][n-1];
    }
};

1155. Number of Dice Rolls With Target Sum

dp[i][j] = dp[i-1][j-k] for k from 1 to f
有点像背包的思路

class Solution {
public:
    int numRollsToTarget(int d, int f, int target) {
        // f[target] = sum(f[target-num[i]]);
        vector<vector<int>> dp(d+1, vector<int> (target+1));
        dp[0][0] = 1; int m = 1e9+7;
        
        for (int i=1; i<=d; i++) {
            for (int j=1; j<=target; j++) {
                for (int k=1; k<=f && k<=j; k++) {
                    dp[i][j] = dp[i][j]%m + dp[i-1][j-k]% m;
                }
            }
        }
        return dp[d][target] % m;
    }
};

https://leetcode.com/problems/number-of-dice-rolls-with-target-sum/discuss/805597/dp-solution-written-in-c%2B%2B(three-loop-and-two-loop)

688. Knight Probability in Chessboard

494. Target Sum Medium

377. Combination Sum IV Medium

935. Knight Dialer Medium

1223. Dice Roll Simulation Medium

416. Partition Equal Subset Sum Medium

808. Soup Servings Medium

790. Domino and Tromino Tiling Medium

801. Minimum Swaps To Make Sequences Increasing

673. Number of Longest Increasing Subsequence Medium

63. Unique Paths II Medium

576. Out of Boundary Paths Medium

1269. Number of Ways to Stay in the Same Place After Some Steps Hard

1220. Count Vowels Permutation Hard

3.区间合并型dp(Merging Intervals)

没看懂 改天回来再搞
Statement: 给定一组数字,考虑到当前数字和从左侧和右侧可获得的最佳值(可以为左右子树),来找到问题的最佳解决方案。就是从小区间出发,然后合并小区间最优。

Approach: 找到每区间的所有最佳解决方案,并返回最佳答案

// from i to j
dp[i][j] = dp[i][k] + result[k] + dp[k+1][j]

// [0,...,n-l-1], [n-l, ..., n-1]

for(int l = 1; l<n; l++) {
   for(int i = 0; i<n-l; i++) {
       int j = i+l;
       for(int k = i; k<j; k++) {
           dp[i][j] = max(dp[i][j], dp[i][k] + result[k] + dp[k+1][j]);
       }
   }
}

1130. Minimum Cost Tree From Leaf Values

class Solution {
public:
    int mctFromLeafValues(vector<int>& arr) {
        int n = arr.size();
        vector<vector<int>> dp(n,vector<int>(n,0));
        vector<vector<int>> max_v(n,vector<int>(n,0));

// maxv[i][j] -> [i, ..., j] 的最大值
        for(int i = 0; i < n ; i ++)
            max_v[i][i] = arr[i];
        for(int d = 1 ; d < n ; d ++)
        {
            for(int i = 0 ; i + d < n ; i++)
            {
                int j = i + d;
                max_v[i][j] = max(max_v[i][j - 1],arr[j]);
            }
        }
// dp[i][j] : arr[i, ..., j]构成的树所有非叶子节点的最小值
        for(int i = 0 ; i < n - 1 ; i ++)
            dp[i][i + 1] = arr[i] * arr[i + 1];
        for(int d = 2 ; d < n ; d ++)
        {
            for(int i = 0 ; i + d < n ; i ++)
            {
                int j = i + d;
                int cur_min = INT_MAX;
                for(int k = i ; k < j ; k ++)
                    cur_min = min(cur_min,dp[i][k] + dp[k + 1][j] + max_v[i][k]*max_v[k + 1][j]);
                dp[i][j] = cur_min; 
            }
        }
        return dp[0][n - 1];
    }
};

作者:T-SHLoRk
链接:https://www.acwing.com/solution/LeetCode/content/3996/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

96. Unique Binary Search Trees Medium

1039. Minimum Score Triangulation of Polygon Medium

546. Remove Boxes Medium

1000. Minimum Cost to Merge Stones Medium

312. Burst Balloons Hard

375. Guess Number Higher or Lower II Medium

4.字符串上的DP

Statement: 给定俩字符串,得到某个结果

Approach: Most of the problems on this pattern requires a solution that can be accepted in O(n^2) complexity.

// i - indexing string s1
// j - indexing string s2
for (int i = 1; i <= n; ++i) {
   for (int j = 1; j <= m; ++j) {
       if (s1[i-1] == s2[j-1]) {
           dp[i][j] = /*code*/;
       } else {
           dp[i][j] = /*code*/;
       }
   }
}

一个字符串的情况

for (int l = 1; l < n; ++l) {
   for (int i = 0; i < n-l; ++i) {
       int j = i + l;
       if (s[i] == s[j]) {
           dp[i][j] = /*code*/;
       } else {
           dp[i][j] = /*code*/;
       }
   }
}

5.决策型DP

The general problem statement for this pattern is forgiven situation decide whether to use or not to use the current state. So, the problem requires you to make a decision at a current state.

Statement:Given a set of values find an answer with an option to choose or ignore the current value.

Approach:If you decide to choose the current value use the previous result where the value was ignored; vice-versa, if you decide to ignore the current value use previous result where value was used.

// i - indexing a set of values
// j - options to ignore j values
for (int i = 1; i < n; ++i) {
   for (int j = 1; j <= k; ++j) {
       dp[i][j] = max({dp[i][j], dp[i-1][j] + arr[i], dp[i-1][j-1]});
       dp[i][j-1] = max({dp[i][j-1], dp[i-1][j-1] + arr[i], arr[i]});
   }
}

https://leetcode.com/discuss/general-discussion/458695/dynamic-programming-patterns#distinct-ways

6.背包问题

y总的闫式dp分析法总结。

01背包

#include <cstdio>
#include <vector>

using namespace std;

int n, m;
vector <vector<int>> dp;
vector<int> prices;
vector<int> cap;

void run() {
    for (int i=1; i<=n; i++) {
        for (int j=1; j<=m; j++) {
            if (j<cap[i-1]) dp[i][j] = dp[i-1][j];
            else dp[i][j] = max(dp[i-1][j], dp[i-1][j-cap[i-1]]+prices[i-1]);
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    prices = vector<int>(n);
    cap = vector<int>(n);
    dp = vector<vector<int>>(n+1, vector<int>(m+1));

    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }

    run();
    printf("%d", dp[n][m]);
}

滑动数组改进

void run() {
    for (int i=1; i<=n; i++) {
        for (int j=m; j>=cap[i-1]; j--) {
        // for (int j=1; j<=m; j++) {
            printf("[%d,%d]->", i, j);
            dp[j] = max(dp[j], dp[j-cap[i-1]]+prices[i-1]);
            for(auto x:dp) printf("%d ", x); printf("\n");
        }
    }
}

对比两个顺序。 如果是从1到m, 由于dp[j]=max(dp[j], dp[j-cap[i-1]]+prices[i-1]), 所以实际上,得到的是全选择第i个物品的最大值,然而只能选择一个。
比如在第一个循环里面,我们想要的dp数组是[0, 2, 2, 2, 2], 而1到m则为[0, 2, 4, 6, 8],在为8时候,实际上4个物品i被选中了显然不符合题意。但是再后面的多重和完全背包问题里就符合题意了。

#include <cstdio>
#include <vector>

using namespace std;

int n, m;
vector<int> dp;
vector<int> prices;
vector<int> cap;

void run() {
    for (int i=1; i<=n; i++) {
        for (int j=m; j>=cap[i-1]; j--) {
            dp[j] = max(dp[j], dp[j-cap[i-1]]+prices[i-1]);
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    prices = vector<int>(n);
    cap = vector<int>(n);
    dp = vector<int> (m+1);

    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }

    run();
    printf("%d", dp[m]);
}

完全背包问题

朴素O(nm^2) ,可把dp放在input处,小小优化。https://www.acwing.com/solution/content/10454/

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<vector<int>> dp;

void run() {
    for(int i=1; i<=n; i++)
        for (int j=1; j<=m; j++) 
            for (int k=0; cap[i-1]*k<=j; k++) // 注意是0开始
                dp[i][j] = max(dp[i][j], dp[i-1][j-cap[i-1]*k] + k*prices[i-1]);
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n);
    prices = vector<int>(n);
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

优化

dp[i][j]= max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w, dp[i - 1][j - 3 * v] + 3 * w);

dp[i][j - v] = max(dp[i - 1][j - v], dp[i - 1][j - 2*v] + w, dp[i - 1][j - 3 * v] + 2 * w);
将每一项一一比对,我们可以得到下列状态表示:
dp[i][j] = max(dp[i - 1][j], dp[i][j - v]+w);注意这里是dp[i]不是dp[i-1]

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<vector<int>> dp;

void run() {
    for(int i=1; i<=n; i++) 
        for (int j=1; j<=m; j++) 
            if (j<cap[i-1])
                dp[i][j] = dp[i-1][j];
            else 
                dp[i][j] = max(dp[i-1][j], dp[i][j-cap[i-1]] + prices[i-1]);
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n);
    prices = vector<int>(n);
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

滚动数组优化

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<int> dp;

void run() {
    for(int i=1; i<=n; i++) 
        for (int j=cap[i-1]; j<=m; j++) // 这次要从小到大
            dp[j] = max(dp[j], dp[j-cap[i-1]] + prices[i-1]);
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n);
    prices = vector<int>(n);
    dp = vector<int> (m+1);
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[m]);
}

滑动数组+input优化

#include <cstdio>
#include <vector>

using namespace std;

int n,m,v,w;
vector<int> dp;

int main() {
    scanf("%d %d", &n, &m);

    dp = vector<int> (m+1);
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &w, &v);
        for (int j=w; j<=m; j++) 
            dp[j] = max(dp[j], dp[j-w] + v);
    }
    
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[m]);
}

多重背包问题

f[i,j] = Max(f[i-1,j], f[i-1,j-v]+w,f[i-1,j-2v]+2w,…,f[i-1,j-sv]+sw)
f[i,j-v]=Max(f[i-1,j-v], f[i-1,j-2v]+w,…, f[i-1,j-(s+1)v]+(s+1)w)
加上了数量的限制后,f[i,j]和f[i,j-v]的关系不好比较

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<int> nums;
vector<vector<int>> dp;

void run() {
    for(int i=1; i<=n; i++) 
        for (int j=1; j<=m; j++) 
            for (int k=0; k*cap[i]<=j && k<=nums[i]; k++) 
                dp[i][j] = max(dp[i][j], dp[i-1][j-k*cap[i]] + k*prices[i]); // 必须是dp[i]不是dp[i-1], dp[i][j-2*cap]+2*w 不一定比dp[i][j-cap]+w大。。
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n+1);
    prices = vector<int>(n+1);
    nums = vector<int>(n+1);
    
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=1; i<=n; i++) {
        scanf("%d %d %d", &cap[i], &prices[i], &nums[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

输入优化

#include <cstdio>
#include <vector>

using namespace std;

int n,m,price,cap,num;
vector<vector<int>> dp;

int main() {
    scanf("%d %d", &n, &m);
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=1; i<=n; i++) {
        scanf("%d %d %d", &cap, &price, &num);
        for (int j=1; j<=m; j++) {
            for (int k=0; k*cap<=j && k<=num; k++) 
                dp[i][j] = max(dp[i][j], dp[i-1][j-cap*k]+k*price);
        }
    }
    
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

利用二进制压缩,退化成0-1背包问题

#include <cstdio>
#include <vector> 

using namespace std;
int n, m, cap, price, num;
vector<pair<int, int>> bins; 
vector<int> dp;


int main()
{
    scanf("%d %d", &n, &m);
    dp = vector<int> (m+1);
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d %d", &cap, &price, &num);
        
        // 二进制优化
        for(int j=1; j<num; num -= j, j<<=1) bins.push_back({j*price, j*cap});
        if(num) bins.push_back({num*price, num*cap});

        // 0-1背包的优化方法
        for (auto [price, v] : bins)
            for(int j=m; j>=v; j--)
                dp[j]=max(dp[j],dp[j-v]+price);
                
        bins.clear();
    }  
    printf("%d", dp[m]);
}

分组背包问题

#include <cstdio>
#include <vector>

using namespace std;

int n,m,num;
vector<vector<int>> dp;

vector<vector<pair<int, int>>> groups;

void run() {
    for(int i=1; i<=n; i++) {
        for (int j=1; j<=m; j++) {
            dp[i][j] = dp[i-1][j]; // dp[i][j] = dp[i-1][j] 只发生一次!放到innermost循环会发生多次,覆盖前面得到的最大值。
            for (auto [v, price] : groups[i-1]) {
                if (j>=v) dp[i][j] = max(dp[i][j], dp[i-1][j-v]+price);
            }
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    dp = vector<vector<int>> (n+1, vector<int>(m+1));
    
    for (int i=0; i<n; i++) {
        scanf("%d", &num);
        vector<pair<int, int>> vec(num);
        for (int j=0; j<num; j++) {
            int v, price;
            scanf("%d %d", &v, &price);
            
            vec[j].first=v;
            vec[j].second=price;
        }
        groups.emplace_back(move(vec));
    }
    run();
    printf("%d", dp[n][m]);
}

i只用到第i-1列,所以可用0-1背包的移动数组优化

#include <cstdio>
#include <vector>

using namespace std;

int n,m,num;
vector<int> dp;

vector<vector<pair<int, int>>> groups;

void run() {
    for(int i=1; i<=n; i++) {
        for (int j=m; j>=1; j--) {
            for (auto [v, price] : groups[i-1]) {
                if (j>=v) dp[j] = max(dp[j], dp[j-v]+price);
            }
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    dp = vector<int>(m+1);
    
    for (int i=0; i<n; i++) {
        scanf("%d", &num);
        vector<pair<int, int>> vec(num);
        for (int j=0; j<num; j++) {
            int v, price;
            scanf("%d %d", &v, &price);
            
            vec[j].first=v;
            vec[j].second=price;
        }
        groups.emplace_back(move(vec));
    }
    run();
    printf("%d", dp[m]);
}
posted @ 2020-08-25 20:29  linsinan1995  阅读(164)  评论(0)    收藏  举报