演算法筆記之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
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;
}
};
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]);
}
滑動陣列改進
#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]);
}