dp

// DP基础

// DP有两种写法———— 递归 / 递推(for循环)
// DP的递归写法(缺点是:不利于节省空间)
// 递归:带有“记忆化搜索(备忘录)”结构的菲波那切数列
	int f(int n){
		if(n == 0 || n == 1) return 1;
		if(dp[n] != -1) return dp[n];
		else{
			dp[n] = f(n - 1) + f(n - 2);  //dp数组用于存放冗余
			return dp[n];
		}
	}
// 菲波那切数列 原代码
	int f(int n){  
		if(n == 0 || n == 1) return 1;
		else return f(n - 1) + f(n - 2);
	}

// 递推:数塔问题
// 因为:例如 路径5->8->7和路径5->3->7都会去计算7下面的路径,造成了冗余(p427),
// 所以令dp[i][j]表示“从第i行第j个数字出发的到达最底层的所有路径中能得到的最大和”,
// ...最后,dp[1][1]就是最终答案。
	const int maxn = 1000;
	int f[maxn][maxn],dp[maxn][maxn];
	int main(){
		int n;
		//输入
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=i;j++){
				scanf("%d",&f[i][j]);
			}
		}
		//边界
		for(int j=1;j<=n;j++){
			dp[n][j] = f[n][j];  //数塔的最后一层的dp值总是等于元素本身,这也是为啥从边界出发的原因
		}
		//从第n+1层计算出n层...不断往上计算出dp[i][j]
		for(int i=n-1;i>=1;i--){
			for(int j=1;j<=i;j++){
				//状态转移方程
				dp[i][j] = max(dp[i + 1][j],dp[i + 1][j + 1]) + f[i][j];
			}
		}
		//dp[1][1]即为需要的答案
		printf("%d\n",dp[1][1]);
		return 0;
	}
	//此题还有递归的写法但省略,递归的结构为:自顶向下(即:例如它会先算dp[1][1])。

// 至此总结为:截止目前为止,DP的引入部分已完结,可以看出不管是在“菲波那切数列”还是“数塔”中,
// DP(dp[]数组)都扮演着存储计算冗余的作用。

// 不管是本书还是教程所说的步骤都是:可以先分析出“暴力算法(包括递归或for循环(二者都无备忘录)两种代码结构)”,
// 再改造出“DP算法(即:在暴力的基础上加上“用于存放冗余的dp数组”的算法)”。

// DP的递推写法总是从边界出发!通过状态转移方程扩散至整个dp数组(p427)。
// 递推利于做节省空间的改造。

// 最大连续子序列和
// 因为发现左端点的枚举是不必要的,所以令dp[]存放以A[i]结尾(强制A[i]结尾p430)的连续序列的最大和(p430)
	const int maxn = 10010;
	int A[maxn],dp[maxn];
	int main(){
		//输入
		int n;
		scanf("%d",&n);
		for(int i=0;i<n;++i){
			scanf("%d",&A[i]);
		}
		//边界
		dp[0] = A[0];
		//状态转移方程
		for(int i=0;i<n;++i){
			dp[i] = max(A[i],dp[i-1]+A[i]);    【 DP's feeling 】  ~ ~ ~ ~
		}
		//输出(遍历出dp[]中的最大值)
		int k = 0;  //下标
		for(int i=0;i<n;i++){
			if(dp[i] > dp[k]){
				k = i;
			}
		}
		printf("%d\n",dp[k]); //对下标的操作
		// int max;
		// for(int i=0;i<n;i++){
		// 	if(dp[i] > max){
		// 		max = dp[i];
		// 	}
		// }
		// printf("%d\n",max); //直接对元素的操作
		return 0;
	}
	
// 最长不下降子序列(Longest Increasing Sequence -- LIS)
// 令dp[]存放以A[i]结尾(强制A[i]结尾p432)的最长不下降子序列的长度(p432)
	const int N = 100;
	int A[N],dp[N];
	int max(int a,int b){
		return a > b ? a : b;  //如果a大于b,则返回a;否则返回b
	}
	int main(){
		//输入
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&A[i]);
		}

		int max = -1;  //记录最大的dp[i]
		for(int i=1; i<=n; i++){  //按顺序计算出dp[i]的值
			dp[i] = 1;  //边界(初始)
			for(int j=1; j<i; j++){ // A[j]们 和 A[i] 依次比较  
				if(A[i] >= A[j] && (dp[j] + 1 > dp[i]) ){  
					dp[i] = dp[j] + 1;
				}
			}
			max = max(max,dp[i]);
		}
		printf("%d\n",max);

		return 0;
	}

// 最长公共子序列(Longest Common Subsequece--LCS)
	const int N = 100;
	char A[N],B[N];  //char A[]
	int dp[N][N];  //二维
	int main(){
		int n;
		gets(A+1);  //从下标为1开始读入  //gets()
		gets(B+1);  //
		int lenA = strlen(A+1);  //读取长度也从下标为1开始  //strlen()
		int lenB = strlen(B+1);  //
		//边界
		for(int i=0;i<=lenA;i++){
			dp[i][0] = 0;
		}
		for(int i=0;i<=lenB;i++){
			dp[0][i] = 0;
		}
		//状态转移方程 ———— dp[i][j]表示:串A的i号位 和 串B的i号位 之前的LCS的长度
		for(int i=1;i<=lenA;i++){
				for(int j=1;i<=lenB;j++){
					if(A[i] == B[j]){
						dp[i][j] = dp[i-1][j-1] + 1;
					}else{
						dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]);  max(dp[i - 1][j],dp[i][j - 1])
					}
			}
		}
		//输出
		printf("%d\n",dp[lenA][lenB]);
		return 0;
	}
// 17蓝桥省赛_CPP/B No.6————最大公共子串
	#include <cstdio> 
	#include <cstring>
	const int N=256;
	int f(const char* s1, const char* s2){
	//int f(const char s1[N], const char s2[N]){ //等效
	//int f(const char s1[], const char s2[]){ //等效
		int a[N][N];  //【a[i][j]同样表示:串s1的第i号位之前和串s2的第j号位之前的最大公共子串的长度】
		int len1 = strlen(s1);
		int len2 = strlen(s2);
		int i,j;
		memset(a,0,sizeof(int)*N*N);  //即 边界:a[0][j] = a[i][0] = 0;
		int max = 0;
		for(i=1;i<=len1;i++){
			for(j=1;j<=len2;j++){
				if(s1[i-1]==s2[j-1]){
					a[i][j]=a[i-1][j-1]+1;  //填空 即:状态转移方程     【即表示:最大公共子串的长度+1 (说法很重要)】
					if(a[i][j]>max) max=a[i][j];
				} 
				// 没有else代码,说明当不相等的话串就断了(a[i][j]值为0),
				// 【区分题目是“串(本题)”还是“序列(BookAN p434最长公共子序列LCS)”】
			}
		}
		return max;
	}
	int main(){
		printf("%d\n", f("abcdkkka", "baabcdadabc"));    //答案 4
		return 0;
	}

// 最长回文子串
	const int maxn = 1010;
	char A[maxn];
	int dp[maxn][maxn];
	int main(){
		gets(S);
		int len = strlen(S),ans = 1;
		memset(dp,0,sizeof(dp));
		//边界
		for(int i=0;i<len;i++){
			dp[i][i] = 1;
			if(i < len - 1){
				if(S[i] == S[i + 1]){
					dp[i][i + 1] = 1;
					ans = 2;
				}
			}
		}
		//状态转移
		//要点在于往后枚举,使得每位都可计算。对于其中的衔接根本不用顾虑,虽然不好理解,但事实就如此。
		for(int L=3;L<=len;L++){  //枚举子串的长度
			for(int i=0;i+L-1<len;i++){  //枚举子串的起始端点(即左端点)
				int j = i + L - 1;  //子串右端点
				if(S[i] == S[j] && dp[i + 1][j - 1] == 1){  //如果(i+1)和(j-1)之间是回文的
					dp[i][j] = 1;  //表示i和j之间是回文的  //否则为0
					ans = L;  //更新最长回文子串长度
				}
			}
		}
		printf("%d\n",ans);
		return 0;
	}

// get!!:在“最长公共子序列”和“最长回文子串”中,dp[i][j]这个二维数组的两个维度(分别)巧妙的表示为:
// (串A的i号位)和(串B的i号位)之前的LCS的长度 ; (串的i号位)和(串的j号位)之间是否回文 。
// 而不必深究二维表每个格子位示图。

背包---------------------------
题目:
有n件物品,每件物品的重量为w[i],价值为c[i],
现有一个容量为V的背包,问如何选取物品放入背包使得背包内的物品的总价值最大,输出最大价值。
	若为:其中每种物品都只有 1件,则用01背包 
	若为:其中每种物品都只有 无穷件,则用完全背包
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 510;
int main() {
	int n, V, w[MAXN], c[MAXN], dp[MAXN][MAXN];
		//dp[i][v]表示前i件物品(1<=i<=n;0<=v<=V)(即:前i件物品中有的放有的不放)
		//恰好(即:放完后可能还有空间,但再加任何一件都装不下)
		//装入容量为V的背包中所能获得的最大价值
	scanf("%d %d",&n, &V);
	for(int i=0; i<n; i++)
		scanf("%d",&w[i]);
	for(int i=0; i<n; i++)
		scanf("%d",&c[i]);
	for(int v=0; v<=V; v++)
		dp[0][v] = 0;
	for(int i=1; i<n; i++)//当前物品
		for(int v=w[i]; v<=V; v++)//当前容量  为什么需要枚举v??????
			dp[i][v] = max(dp[i-1][v], dp[i-1][v-w[i]] + c[i]);//01背包 
			//若为完全背包:dp[i][v] = max(dp[i-1][v], dp[i][v-w[i]] + c[i]);
				//不同之处:完全背包里选择当前物品后的物品数量是不变的(无穷件)
	int maxmial = -1;
	for(int v=1; v<=V; v++)
		maxmial = max(maxmial, dp[n-1][v]);
	printf("%d\n",maxmial);
	//或直接输出:printf("%d %d",dp[n-1][V]);
	return 0;
}
//01包样例: 
// 5 8
// 3 5 1 2 2
// 4 5 2 1 3
//输出10 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值