动态规划学习之旅

  1. 什么是动态规划
    动态规划也称DP是一种用来解决一类最优化问题的算法思想,它将一个复杂的问题分解成若干子问题,动态规划将每个子问题的解记录下来,最后综合子问题的最优解得到该问题的最优解。同时需要注意的是如果一个问题被分解成了若干子问题并且这些子问题会重复出现,那么称这些问题为重叠子问题(overlapping subproblems)一个问题必须拥有重叠子问题,才能使用动态规划去解决。
    另外还有最优子结构(optimal substructure),最优子结构是指一个问题的最优解可以由它子问题的最优解得到,所以最优子结构保证了动态规划的最优解的可能性,因此一个问题必须拥有最优子结构才可以使用动态规划去解决。
    2.经典问题
    数塔问题
    数塔问题即计算从最上边的数字到底层数字所有数字相加后的最大值是多少。
#include<iostream>
#include<algorithm>
#include<cmath>
#define maxn 1005
using namespace std;
int f[maxn][maxn],dp[maxn][maxn];//f数组表示第i行第j列数字是多少,dp数组表示从第i行第j列到最
//底层所有路径中能得到的最大值
int main( )
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		cin>>f[i][j];
	}
	for(int i=1;i<=n;i++)
	dp[n][i]=f[n][i];
	
	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];
		}
	}
	cout<<dp[1][1]<<endl;
	return 0;
}

最大连续子序列和
给出一个数字序列求出这个序列中最大和。

#include<iostream>
#include<algorithm>
#include<cmath>
#define maxn 1005
using namespace std;
int f[maxn],dp[maxn];//f[i]用来存数据,dp[i]表示以f[i]为结尾的连续序列的最大和
int main( )
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>f[i];
	dp[0]=f[0];
	for(int i=1;i<n;i++)
	{
		dp[i]=max(f[i],dp[i-1]+f[i]);
	}
	int k=0;
	for(int i=1;i<n;i++)
	{
		if(dp[i]>dp[k])
		k=i;
	}
	cout<<dp[k]<<endl;
	return 0;
}

对于最短连续子序列和,用的是尺取法,这里转载一个大佬的博客:
尺取法详解加例题

最长不下降子序列(LIS)
在一个数字序列中找一个最长的子序(可以不连续),使得这个子序列是不下降的(非递减)。

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int a[100],dp[100];//dp数组表示以a[i]结尾的最长不下降子序列
int main( )
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	int ans=-1;
	for(int i=1;i<=n;i++)
	{
		dp[i]=1;
		for(int j=1;j<i;j++)
		{
			if(a[i]>=a[j]&&(dp[j]+1>dp[i]))
			dp[i]=dp[j]+1;
		}
		ans=max(ans,dp[i]);
	}
	cout<<ans<<endl;
	return 0;
 } 

最长公共子序列(LCS)
给定两个字符串A和B,求一个字符串,使得这个字符串是A和B的最长公共部分(子序列可以不连续)。
dp数组

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
char a[100],b[100];
int dp[100][100];
int main( )
{
	gets(a+1);
	gets(b+1);
	int lena=strlen(a+1);
	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;
	
	for(int i=1;i<=lena;i++)
	{
		for(int j=1;j<=lenb;j++)
		{
			if(a[i]==b[j])
			dp[i][j]=dp[i-1][j-1]+1;
			else
			dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
		}
	 } 
	 cout<<dp[lena][lenb]<<endl;
	 return 0;
} 

最长回文字串
给出一个字符串S,找出S的最长回文子串的长度。
dp[i][j]表示s[i]到s[j]所表示的子串是否为回文子串。
如果s[i]==s[j],那么只要s[i+1]到s[j-1]时回文子串,s[i]到s[j]也是回文子串 ;如果s[i]!=s[j]那么s[i]到s[j]一定不是回文子串。

#include<iostream>
#include<cstring>
using namespace std;
char s[1010];
int dp[1010][1010];
int main( )
{
	gets(s);
	int len=strlen(s);
	int ans=1;
	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 i=3;i<=len;i++)
	{
		for(int j=0;j+i-1<len;j++)
		{
			int k=j+i-1;
			if(s[j]==s[k]&&dp[j+1][k-1]==1)
			{
				dp[j][k]=1;
				ans=i;
			}
		}
	}
	cout<<ans<<endl;
	return 0;
 } 

01背包问题
问题描述:有N件物品,每件物品的重量是w[i],价值为c[i]。现在有一个容量是V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品只有一件。
思路:动态规划:B(k,W)其中k表示前k个物品,W表示剩下多少容量,因此分成三种情况:(1)当第k件物品太重,则B(k,W)=B(k-1,W);
(2)选择拿第k件物品,则B(k,W)=B(k-1,W-w[k])+c[k],其中其中W-w[k]表示比原来少了w[k]的空间。
(3)选择不拿第k件物品,则B(k,W)=B(k-1,W)
代码如下:

#include<iostream>
using namespace std;
int B[100][100];
int w[10];
int v[10];
int main( )
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	cin>>w[i];
	
	for(int i=1;i<=n;i++)
	cin>>v[i];
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=k;j++)
		{
			if(w[i]>j)
			B[i][j]=B[i-1][j];
			else
			{
				int value1=B[i-1][j-w[i]]+v[i];
				int value2=B[i-1][j];
				if(value1>value2) B[i][j]=value1;
				else B[i][j]=value2;
			}
		}
	}
	cout<<B[5][20]<<endl;
	return 0;
}

这里01背包问题的空间复杂读可以优化,原因:
每次计算当前最大价值时只与上一次计算结果有关,与上上次等前边的没有关系,因此使用二维数组就产生了浪费,完全可以使用一维数组进行代替,但是在代替的时候为了避免以前的结果影响本次,因此需要将价值从后往前进行逆序排列。
优化后的代码:

for(int i=1;i<=n;i++)
{
	for(int j=k;j>=1;j--)
	{
		dp[v]=max(dp[j],dp[j-w[i]]+v[i]);
	}
}

完全背包问题
问题:有N件物品,每件物品的重量是w[i],价值为c[i]。现在有一个容量是V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都有无穷件。
思路:对于第i件物品来说:如果不放第i件物品,那么dp[i][v]=dp[i-1][v];
如果放第i件物品。这里的处理与01背包有所不同,因为01背包每个物品只能选一次,而完全背包可以选无数个,所以完全背包的转移方程就变为:dp[i][v]=max(dp[i-1][v],dp[i][v-w[i]]+c[i])同样的也可以优化为一维数组,但是需要注意的是价值是从正向枚举。
核心代码:

for(int i=1;i<=n;i++)
{
	for(int v=w[i];v<=V;v++)
	{
		dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
	}
}
发布了29 篇原创文章 · 获赞 2 · 访问量 610

猜你喜欢

转载自blog.csdn.net/qq_44722533/article/details/104060656
今日推荐