算法:动态规划

10 阅读4分钟

动态规划

无后效性,子问题重叠性,最优子结构性质,是问题能用动态规划求解的三个基本条件

无后效性:为了保证计算能够按顺序、不重复地进行,动态规划要求已经求解的子问题不受后续阶段的影响。

状态,阶段和决策是构成动态规划算法的三要素

动态规划是全局最优解

贪心是局部最优解

动态规划算法把相同的计算过程作用于各阶段的同类子问题,就好比,把一个公式在若干个输入数据上运行。六个字,状态转移方程!!!

57bxgwwa.png

线性动态规划经典例题:

LIS问题(最长上升子序列问题)

B3637 最长上升子序列

LCS问题(最长公共子序列问题)

P1439 【模板】最长公共子序列大佬发的题解很有意思

数字三角形

P9541 「AWOI Round 2 D」数字三角形

P1216 [USACO1.5] [IOI1994]数字三角形 Number Triangles 转存失败,建议直接上传图片文件

背包

01背包

P1048 [NOIP2005 普及组] 采药

P1049 [NOIP2001 普及组] 装箱问题

例题:01背包

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1010;
ll n,m;
ll v[N],w[N];
ll res;
ll f[N][N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<=m;j++)
		{
			if(j < v[i])
			{
				f[i][j] = f[i+1][j];
			}
			else if(j >= v[i])
			{
				f[i][j] = max(f[i+1][j],f[i+1][j-v[i]]+w[i]);
			}
		}
	}
	cout<<f[1][m]<<"\n";
	return 0;
}
优化空间,二维变一维
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1010;
ll n,m;
ll v[N],w[N];
ll res;
ll f[N];
ll dp[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<=m;j++)
		{
			dp[j] = f[j];
			if(j >= v[i])
			{
				dp[j] = max(f[j],f[j-v[i]]+w[i]);
			}
		}
		memcpy(f,dp,sizeof f);
	}
	cout<<f[m]<<"\n";
	return 0;
}

完全背包

例题:P1616 疯狂的采药

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e4+5;
const ll M = 1e7+5;
ll T,n;
ll t[N],w[N];
ll dp[M];
int main()
{
	cin >> T >> n;
	for(int i = 1; i <= n; i++)
	{
		cin >> t[i] >> w[i];
	}
	for(int i = 1; i <= n; i++)
	{
		for(int j = t[i] ; j <= T; j++)
		{
			dp[j] = max(dp[j],dp[j - t[i]] + w[i]);
		}
	}
	cout << dp[T];
	return 0;
 } 

例题:P1164 小A点菜

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e2+5;
ll n,m;
ll a[N];
ll f[1005];
ll g[1005];
//ll mem[N][1005];
//ll dfs(ll x,ll spm)
//{
//	if(mem[x][spm]) return mem[x][spm];
//	ll sum = 0;
//	if(x>n) sum = 0;
//	else if(spm == a[x])
//	{
//		sum = dfs(x+1,spm)+1;
//	}
//	else if(spm > a[x])
//	{
//		sum = dfs(x+1,spm)+dfs(x+1,spm-a[x]);
//	}
//	else if(spm < a[x])
//	{
//		sum = dfs(x+1,spm);
//	}
//	mem[x][spm] = sum;
//	return sum;
//}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
//	ll res = dfs(1,m);
//	cout<<res;
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<=m;j++)
		{
			g[j] = f[j];
			if(j == a[i])
			{
				g[j] = f[j] + 1;
			}
			else if(j > a[i])
			{
				g[j] = f[j] + f[j-a[i]];
			}
			else if(j < a[i])
			{
				g[j] = f[j];
			}
		}
		memcpy(f , g , sizeof f);
	}
	cout<<f[m];
	return 0;
}
//最长公共子序列问题 O(n^2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e3+5;
ll n;
ll a[N];
ll b[N];
ll dp[N][N];
ll mx = -1;
int main()
{
	cin >> n;
	for(int i = 1;i <= n; i++)
	{
		cin >> a[i];
	}
	for(int i = 1; i <= n; i++)
	{
		cin >> b[i];
	}
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; 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[n][n];
	return 0;
}

滚动数组

代码1(交替滚动)
int dp[2][N];
int solve(int n , int c)
{
  int now = 0 , old = 1;		//now 指向当前正在计算的一行,old指向旧的一行
  for(int i = 1; i <= n; i++)
  {
    swap(old, now);			//交替滚动,now始终指向最新的一行
    for(int j = 0; j <= c; j++)
    {
      if(j < a[i])
        dp[now][j] = dp[old][j];
      else
        dp[now][j] = max(dp[old][j], dp[old][j - a[i]]+ w[i]);
    }
  }
       return dp[now][c]; //返回最新的行                                 
}
代码2(自我滚动)
int dp[N];
int solve(int n , int c)
{
 	for(int i = 1; i <= n; i++)
      for(int j = c; j >= a[i]; j--)
      {
      	dp[j] = max(dp[j],dp[j - a[i]] + w[i];  
      }
     return dp[c];
}