DP入门题
PATA1007
题目要求求出最大子序列的各元素之和,并且输出最大子序列的第一个元素和最后一个元素的值。使用一个dp数组,dp[i]表示以第i个元素为末尾的和最大的序列。由于需要用到序列的首元素,所以在DP时就要记录。状态转移方程如下:
\(dp[0].start=0;dp[0].v=dat[0]\)
\(dp[i-1]<0,dp[i].start=i,dp[i].v=dat[i]\)
\(dp[i-1]\geq 0,dp[i].start=dp[i-1].start,dp[i].v=dat[i]+dp[i-1].v\)
题目要求要求按照一定的优先级输出结果,即最大值、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个元素结尾的所有序列的最大长度,可以写出状态转移方程:
\(dp[i]=MAX\{1,dp[j]+1\},j\in \{0...i-1\} \and j的顺序先于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;
}
不难看出,两种写法的时间复杂度都是\(O(L^2)\),这个无疑是很大的,所以不出所料这两个版本的代码在acwing的oj中都会超时,但pat的水oj可以ac。接下来介绍可以在\(O(ML)\)的时间复杂度中求解的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位为结束的字串两个的最长公共子串长度。
状态转移方程为
\(若s1[i]=s2[j],dp[i][j]=max\{dp[i-1][j],dp[i][j-1]\}+1\)
\(否则,dp[i][j]=max\{dp[i-1][j],dp[i][j-1]\}\)
代码如下:
#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为首尾的字串是否为回文字串
状态转移方程:
\(若str[i]=str[j],则dp[i][j]=dp[i+1][j-1]\)
边界条件:\(dp[i][i]=1,dp[i][i+1]=str[i]==str[i+1]?1:0\)
比较重要的是它的遍历方法,不能用之前的递增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;
}

 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号