DP入门题
PATA1007
题目要求求出最大子序列的各元素之和,并且输出最大子序列的第一个元素和最后一个元素的值。使用一个dp数组,dp[i]表示以第i个元素为末尾的和最大的序列。由于需要用到序列的首元素,所以在DP时就要记录。状态转移方程如下:
题目要求要求按照一定的优先级输出结果,即最大值、i、j,那么可以使用三轮遍历,也可以自定义一个比较函数使用sort(时间复杂度会增加)。
代码如下:
#include<iostream> #include<string> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; const int MAX = 10010; int K = 0; struct node { int start, end, v; node(int a,int b,int c):start(a),end(b),v(c){} node(){} }; //dp数组,第i个元素储存以i为末尾的序列的最大值 struct node dp[MAX]; //原始data int dat[MAX] = { 0 }; //全为负数? bool flag = true; void input() { cin >> K; for (int i = 0; i < K; i++) { cin >> dat[i]; if (dat[i] >= 0) flag = false; } } struct cmp { bool operator()(const struct node& a, const struct node& b) { if (a.v != b.v) return a.v > b.v; if (a.start != b.start) return a.start < b.start; if (a.end != b.end) return a.end < b.end; } }; int main(void) { ios::sync_with_stdio(false); input(); if (flag) { cout << 0 << " " << dat[0] << " " << dat[K - 1] << endl; return 0; } //dp计算 dp[0] = node(0, 0, dat[0]); for (int i = 1; i < K; i++) { int s, v; if (dp[i - 1].v >= 0) { s = dp[i - 1].start; v = dp[i - 1].v + dat[i]; } else { s = i; v = dat[i]; } dp[i] = node(s, i, v); } //输出结果 //将结果排序 sort(dp, dp + K, cmp()); cout << dp[0].v <<" "<< dat[dp[0].start] <<" "<< dat[dp[0].end]<<endl; }
patA1045之LIS做法
使用最长不下降子序列做法,dp[i]储存以第i个元素结尾的所有序列的最大长度,可以写出状态转移方程:
关键在于如何表示顺序的先后关系。代码1的思路是使用一个二维数组,[i][j]的意义即为数字j允许出现在数字i之前。但这样代码较为冗长。
代码1
#include<iostream> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<unordered_map> #include<vector> #include<set> using namespace std; const int MAX = 10010; const int MAX2 = 205; int N, M, L; //newl储存去掉所有不喜欢的颜色后的色带元素个数 int newl; //map[i][j]表示j可以出现在i之前 bool map1[MAX2][MAX2] = { false }; //储存去掉不喜欢颜色后的色带 vector<int> dat; //储存喜欢的颜色 vector<int> color; set<int> colorset; void input() { cin >> N; cin >> M; for (int i = 0; i < M; i++) { int c; cin >> c; color.push_back(c); colorset.insert(c); } cin >> L; for (int i = 0; i < L; i++) { int c; cin >> c; if (colorset.find(c) != colorset.end()) { dat.push_back(c); } } } //创建映射 void createmap() { for (int i = 0; i < color.size(); i++) { int key = color[i]; for (int j = 0; j <= i; j++) { map1[key][color[j]] = true; } } } int dp[MAX] = { 0 }; int main(void) { ios::sync_with_stdio(false); input(); createmap(); //dp求解,dp[i]储存以色带第i个元素为末尾且满足顺序的所有序列中,序列的最大长度 dp[0] = 1; for (int i = 1; i < dat.size(); i++) { int dpmax = 0; for (int j = 0; j < i; j++) { //元素j的顺序在i之前 if (map1[dat[i]][dat[j]]) { if (dp[j] > dpmax) dpmax = dp[j]; } } dp[i] = (dpmax == 0 ? 1 : dpmax+1); } int result=*max_element(dp,dp+dat.size()); cout << result << endl; }
代码2借鉴了教材。使用一个数组来完成数字到顺序间的映射关系。然后在dp时直接按照映射得到相对的顺序。
代码如下:
#include<iostream> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<unordered_map> #include<vector> #include<set> using namespace std; const int MAX = 10010; const int MAX2 = 205; int N, M, L; //储存去掉不喜欢颜色后的色带 vector<int> dat; //映射,将喜欢的颜色映射到0,1,2... int myhash[MAX2]; void input() { cin >> N; cin >> M; for (int i = 0; i < M; i++) { int c; cin >> c; myhash[c] = i; } cin >> L; for (int i = 0; i < L; i++) { int c; cin >> c; if (myhash[c]!=-1) { dat.push_back(c); } } } int dp[MAX] = { 0 }; int main(void) { fill(myhash, myhash + MAX2, -1); ios::sync_with_stdio(false); input(); //dp求解,dp[i]储存以色带第i个元素为末尾且满足顺序的所有序列中,序列的最大长度 dp[0] = 1; for (int i = 1; i < dat.size(); i++) { int dpmax = 0; for (int j = 0; j < i; j++) { //元素j的顺序在i之前 if (myhash[dat[i]]>=myhash[dat[j]]) { if (dp[j] > dpmax) dpmax = dp[j]; } } dp[i] = (dpmax == 0 ? 1 : dpmax+1); } int result=*max_element(dp,dp+dat.size()); cout << result << endl; }
不难看出,两种写法的时间复杂度都是,这个无疑是很大的,所以不出所料这两个版本的代码在acwing的oj中都会超时,但pat的水oj可以ac。接下来介绍可以在的时间复杂度中求解的LCS做法。
patA1045之LCS做法
LCS即最长公共子串。假设有两个字符串s1和s2分别长为M和N,s1=acb,s2=abbcccc,则最长公共子串为acccc。
使用一个dp数组dp[M+2][N+2],其中dp[i][j]表示以s1的第i位为结尾的字串和s2的第j位为结束的字串两个的最长公共子串长度。
状态转移方程为
代码如下:
#include<cstring> #include<cstdio> #include<string> #include<iostream> #include<vector> using namespace std; const int MAX1 = 205; const int MAX2 = 10005; int N, M, L; int dp[MAX1][MAX2] = { 0 }; //标记第i个颜色是否为喜欢的 bool like[MAX1] = { false }; //储存去掉不喜欢的颜色后的色带 vector<int> mydata; //储存喜欢的色带 int order[MAX1] = { 0 }; void input() { cin >> N; cin >> M; for (int i = 1; i <= M; i++) { int j; cin >> j; like[j] = true; order[i] = j; } cin >> L; mydata.push_back(0); for (int i = 0; i < L; i++) { int j; cin >> j; if (like[j]) mydata.push_back(j); } } int main(void) { ios::sync_with_stdio(false); input(); for (int i = 1; i <= M; i++) { for (int j = 1; j < mydata.size(); j++) { if (order[i] == mydata[j]) { dp[i][j]=max(dp[i-1][j],dp[i][j-1])+1; } else { dp[i][j]= max(dp[i - 1][j], dp[i][j - 1]); } } } cout << dp[M][mydata.size()-1] << endl; }
PATA1524
这道题如果用DP的方法,就是最长回文子串模型。
使用一个bool类型的二维dp数组,其中dp[i][j]表示以i、j为首尾的字串是否为回文字串
状态转移方程:
边界条件:
比较重要的是它的遍历方法,不能用之前的递增i,j的方法,而是每次对一个长度的字串进行dp,每轮结束递增该长度。
代码如下:
#include<iostream> #include<string> #include<cstring> #include<cstdio> using namespace std; const int MAX = 1005; string str; //dp[i][j]表示字串中第i个与第j个中间的子串是否为回文 bool dp[MAX][MAX] = { 0 }; int result=1; //最长回文子串的长度 int main(void) { ios::sync_with_stdio(false); //注意不能直接用cin输入给str,否则得到的是单词而不是一整行 getline(cin, str); int len = str.length(); //初始化dp矩阵的边界值 for (int i = 0; i < len; i++) { dp[i][i] = true; if (str[i] == str[i + 1]) { dp[i][i + 1] = true; result = 2; } } //dp递推 for (int l = 3; l <= len; l++) { for (int i = 0; i <= len - l; i++) { int begin = i; int end = i + l - 1; if (str[begin] == str[end]) { if (dp[begin + 1][end - 1]) { dp[begin][end] = true; result = l; } } } } cout << result << endl; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)