20230417 训练记录:dp
Posted Patricky
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20230417 训练记录:dp相关的知识,希望对你有一定的参考价值。
你也许会好奇为什么没有 04.16 的训练记录,昨天农了一天 O(∩_∩)O。
Vacation
小明在接下来的 \\(n\\) 天,可以选择三种事件并获得 \\(a_i / b_i / c_i\\) 的快乐值,但是他不能连续两天及以上做同样的事。问最大的欢乐值是多少?
\\(n \\leq 10^5; a_i, b_i, c_i \\leq 10^4\\)。
换句话说就是每天做的事情都不一样。用 \\(f_i, j\\) 表示第 \\(i\\) 天做 \\(j\\) 事件所得到的最大快乐值,枚举今天和明天做的事件,即:
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main()
std::cin.tie(nullptr)->sync_with_stdio(false);
int n;
std::cin >> n;
std::vector<std::array<int, 3>> h(n);
for (int i = 0; i < n; i++)
std::cin >> h[i][0] >> h[i][1] >> h[i][2];
std::vector<std::array<int, 3>> f(n + 1);
for (int i = 0; i < n; i++)
for (int j = 0; j < 3; j++)
for (int k = 0; k < 3; k++) if (j != k)
f[i + 1][j] = std::max(f[i + 1][j], f[i][k] + h[i][k]);
std::cout << *std::max_element(f.back().begin(), f.back().end()) << \'\\n\';
return 0;
Knapsack 1
01 背包。
\\(n \\leq 100, w \\leq 10^5, v_i \\leq 10^9\\)。
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main()
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, w;
std::cin >> n >> w;
std::vector<ll> f(w + 1);
for (int i = 0; i < n; i++)
int wi, ci;
std::cin >> wi >> ci;
for (int j = w; j >= wi; j--)
f[j] = std::max(f[j], f[j - wi] + ci);
std::cout << f.back() << \'\\n\';
return 0;
Knapsack 2
01 背包。
\\(n \\leq 100, w \\leq 10^9, v_i \\leq 10^3\\)。
考虑 \\(f_i, j\\) 为前 \\(i\\) 个物品,得到 \\(j\\) 价值的最小容量。
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main()
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, w;
std::cin >> n >> w;
const int N = n * 1000;
std::vector<std::vector<int>> f(n + 1, std::vector<int>(N + 1, 0x3f3f3f3f));
f[0][0] = 0;
for (int i = 0; i < n; i++)
int wi, vi;
std::cin >> wi >> vi;
for (int j = 0; j <= N; j++)
f[i + 1][j] = std::min(f[i + 1][j], f[i][j]);
if (j + vi <= N)
f[i + 1][j + vi] = std::min(f[i + 1][j + vi], f[i][j] + wi);
for (int i = N; ~i; i--)
if (f[n][i] <= w)
std::cout << i << \'\\n\';
std::exit(0);
return 0;
LCS
求两字符串 \\(s, t\\) 的最长公共子序列。
Longest Path
Grid 1
Coins
Sushi
Stones
Deque
Candies
Slimes
同合并石子,区间 dp 模板题。满足四边形不等式。
展开代码
#include <bits/stdc++.h>
int read()
int x = 0, f = 1, c = getchar();
for (; !isdigit(c); c = getchar()) if (c == \'-\') f = -1;
for (; isdigit(c); x = x * 10 + c - \'0\', c = getchar());
return x * f;
const int N = 410;
using ll = long long;
const ll inf = 1E18;
int n, a[N], s[N][N];
ll f[N][N], sum[N];
#define w(i, j) sum[j] - sum[i - 1]
int main()
n = read();
for (int i = 1; i <= n; i++)
a[i] = read();
for (int i = 1; i <= n; i++)
sum[i] = sum[i - 1] + a[i];
s[i][i] = i;
for (int l = 2; l <= n; l++)
for (int i = 1, j = l; j <= n; i++, j++)
f[i][j] = inf;
for (int k = s[i][j - 1]; k <= s[i + 1][j]; k++)
if (f[i][j] > f[i][k] + f[k + 1][j] + w(i, j))
f[i][j] = f[i][k] + f[k + 1][j] + w(i, j);
s[i][j] = k;
printf("%lld\\n", f[1][n]);
return 0;
Matching
Indepedent Set
Flowers
Walk
Digit Sum
Permutation
Grouping
Subtree
Intervals
Tower
Grid 2
Frog 3
dp暑假专题 训练记录
A 回文串的最小划分
题意:给出长度不超过1000的字符串,把它分割成若干个回文字串,求能分成的最少字串数。
#include <iostream> #include <cstdio> #include <string.h> #include <string> using namespace std; const int mod = 1e9 + 7; const int maxn = 1000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; int dp[maxn]; //根据回文串的特性,两边根据中心对称。于是我们扫描回文串的每个中心,寻找他的回文串,用DP来记录更新 //dp[n] 表示到n为止可以分割出多少个回文串 //如果[Left , Right]是回文串, //dp[Right] = min(dp[ Right ] , dp[Left-1] + 1); int main() { int n; cin >> n; while ( n-- ) { char s[maxn]; cin >> s+1; int L = strlen(s+1); for(int i=1;i <= L;i++) dp[i] = i; for(int i=1;i <= L;i++) { for(int j=i,k=i; j <= L && k > 0 ;j++ ,k--)//这种是aabcc形的回文串 { if(s[j] == s[k] ) dp[j] = min(dp[j],dp[k-1] +1); else break; } for(int j=i+1,k=i; j <= L && k > 0 ;j++,k--)//这种是aabb形的回文串 { if(s[j] == s[k] ) dp[j] = min(dp[j],dp[k-1] +1); else break; } } cout<< dp[L]<<endl; } return 0; }
B - LIS变形
题意:给定一串数字,求一个子串满足一下要求:子串的长度是L=2*n+1,前n+1个数字是严格的递增序列,后n+1个数字是严格的递减序列,例如123454321就是满足要求的一个子串,
输出满足要求的最长的L
思路:正着走一遍LIS,再倒着走一遍LIS,a1[i] 表示正着走的 到i的最长递增子序列的长度,a2[i] 表示倒着着走的 到i的最长递增子序列的长度
//本题 二分 + LIS 需要巩固一下
#include <iostream> #include <cstdio> using namespace std; const int mod = 1e9 + 7; const int maxn = 10000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; int n,len; int s1[maxn],s2[maxn]; int a1[maxn],a2[maxn]; int B[maxn]; int b_s(int k) { int ans = 0; int l = 0,r = len-1; //这里注意 右区间是 len-1 while ( l <= r) { int mid = (l+r)/2; if(B[mid] >= k ) ans = mid,r = mid-1; //如果k<= B[mid] 往左边查找 else l = mid +1; } return ans ; } void Lis(int *s,int *a) //a数组存取到i位 的最长递增子序列个数 { B[0] = s[0]; len = 1; for(int i=0;i < n;i++) { if(s[i] > B[len-1]) { B[len++] = s[i]; } else{ int pos = b_s (s[i]); //二分找s[i]的插入位置 B[pos] = s[i]; } a[i] = len; } } int main() { while (cin >> n) { for(int i=0;i < n;i++){ cin >> s1[i]; s2[n-1-i] = s1[i];//把序列倒着读 } Lis(s1,a1), Lis(s2,a2); //longest increasing substring 最长递增子序列函数 int ans = 0; for(int i=0;i < n;i++) { ans = max(ans, min(a1[i],a2[n-1-i])); //ans 是 ans 与 当前位 左右最长递增 和 最长递减的较小者 } cout<< 2*ans -1 <<endl; } return 0; }
C - 区间dp or LCS的变形
题意:给你一个字符串, 求最长的回文子序列, 若答案不唯一, 则输出字典序最小的
思路1:区间dp, dp[i][j]表示字符串s[i]到s[j]构成回文子序列学删除的最小字符数, path[i][j] 字符串s[i]到s[j]可构成的最长回文子序列
#include <iostream> #include <cstdio> #include <string.h> #include <string> using namespace std; const int mod = 1e9 + 7; const int maxn = 1000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; //区间dp dp[i][j] 表示字符串s[i]到s[j]构成回文子序列学删除的最小字符数 //path[i][j] 字符串s[i]到s[j]可构成的最长回文子序列 string path[maxn][maxn]; int dp[maxn][maxn]; char s[maxn]; int main() { while (cin >> s+1) { int L = strlen(s+1); for(int i=L;i >= 1;i--) //这个查找是从 左下到右上进行的 { dp[i][i] = 0;//i到i不用删除 path[i][i] = s[i];//i到i的最长回文子序列就是本身 for(int j=i+1;j <= L;j++) { if(s[i] == s[j]) //就是 s[i][j]这两个字符相同 可以构成回文串 { dp[i][j] = dp[i+1][j-1]; //所以这时候 需要删除的就是左下角的 path[i][j] = s[i] + path[i+1][j-1] +s[j];//只有这一步是构造回文串的 } else if(dp[i+1][j] > dp[i][j-1]) { dp[i][j] = dp[i][j-1] + 1; path[i][j] = path[i][j-1]; } else if(dp[i+1][j] < dp[i][j-1]) { dp[i][j] = dp[i+1][j] + 1; path[i][j] = path[i+1][j]; } else{ dp[i][j] = dp[i+1][j] + 1; path[i][j] = min(path[i][j-1], path[i+1][j]); } } /* for(int a = 1;a<=L;a++) { for(int b= 1;b<=L;b++) { cout<< path[a][b]<<" "; } cout<<endl; } cout<<"----------" <<endl; */ } cout << path[1][L] <<endl; } return 0; }
思路2:转化为lcs, 用结构体进行保存,一维只长度, 一维指构成的字符串
我们都知道把一个字符串逆序后和原字符串进最长公共子序列,可以计算出它的最长回文串长度。这题不仅要输出回文串,而且还要求是字典序最小的,所以挺难搞的。
设str1是正序字符串,str2是逆序后的字符串
f[i][j].len 表示str1的前i位,str2的前j位,最长公共子串的长度
f[i][j].str 表示str1的前i位,str2的前j位,最长公共子串的最小字典序的字符串
状态转移和正常的LCS差不多,只不过增加了记录字典序最小的字符串
但是最终的f[i][j].str却并不一定是答案,因为计算出来的最长公共子序列不一定就是回文串
例如:
kfclbckibbibjccbej
jebccjbibbikcblcfk
bcibbibc是他们的LCS,但是却不是回文串
但是它的前len/2个一定是回文串的前半部分
#include <cstdio> #include <string> #include <cstring> #include <iostream> #include <algorithm> using namespace std; using namespace std; const int mod = 1e9 + 7; const int maxn = 1000 + 5; const int INF = 0x3f3f3f3f; typedef long long LL; struct node{ int len; string s; }dp[maxn][maxn]; char s1[maxn],s2[maxn]; int main () { while (cin >> s1+1) { int L = strlen(s1+1); strcpy(s2+1,s1+1); reverse(s2+1,s2+1+L); for(int i=1;i <= L;i++) { for(int j=1;j <= L;j++) { if(s1[i] == s2[j]){ dp[i][j].len = dp[i-1][j-1].len+1; dp[i][j].s = dp[i-1][j-1].s + s1[i]; } else if(dp[i-1][j].len < dp[i][j-1].len){ dp[i][j].len = dp[i][j-1].len; dp[i][j].s = dp[i][j-1].s; } else if(dp[i-1][j].len > dp[i][j-1].len){ dp[i][j].len = dp[i-1][j].len; dp[i][j].s = dp[i-1][j].s; } else{ dp[i][j].len = dp[i-1][j].len; dp[i][j].s = min( dp[i-1][j].s ,dp[i][j-1].s); } } } string s = dp[L][L].s; int l =dp[L][L].len; if( l & 1) //l是奇数,所以字符串长度是偶数 { for(int i=0;i<l/2;i++) cout<<s[i]; for(int i=l/2;i>=0;i--) cout<<s[i]; cout<<endl; } else{ for(int i=0;i<l/2;i++) cout<<s[i]; for(int i=l/2-1;i>=0;i--) cout<<s[i]; cout<<endl; } } return 0; }
以上是关于20230417 训练记录:dp的主要内容,如果未能解决你的问题,请参考以下文章