动态规划

动态规划dp

DAY1第一天学,啥也不会

DAY2来划水了

今天好像会了 01背包和完全背包和混合背包

01背包就是这个物品只能是选择或者不选的状态,没有别的状态,所以只需要从右往左扫一遍找到最后一个输出即可

#include<bits/stdc++.h>
using namespace std;
int n,m;
int w[1100],c[1100],dp[1100];
int N,M;

int main()
{
	cin>>M>>N;
	for(int i=1;i<=N;i++)
	cin>>w[i]>>c[i];
	for(int i=1;i<=N;i++)
	for(int j=M;j<=w[i];j--)
	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
	cout<<dp[M];
	return 0;
}

完全背包就是每次赋值完之后后面可能还会重新用到,所以从左往右扫,还是一样,输出dp[M]

#include<bits/stdc++.h>
using namespace std;

int w[1100],c[1100],dp[1100];

int N,M;

int main()
{
	cin>>M>>N;
	for(int i=1;i<=N;i++)
	cin>>w[i]>>c[i];
	for(int i=1;i<=N;i++)
	for(int j=w[i];j<=M;j++)
	dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
	cout<<"max="<<dp[M];
	return 0;
}

还有就是加上别的判断条件的,我觉得就是再加上一层for循环的事,至少现在是这样,不然就是if else 来判断的;

upd 2022.8.3 重新学dp
upd 2022.8.12 温习了一下区间dp和树形dp,对dp有了一定的巩固

能用动态规划解决的问题,需要满足三个条件:最优子结构,无后效性和子问题重叠。

1.最优子结构:

具有最优子结构也可能是适合用贪心的方法求解。

注意要确保我们考察了最优解中用到的所有子问题。

证明问题最优解的第一个组成部分是做出一个选择;
对于一个给定问题,在其可能的第一步选择中,假定你已经知道哪种选择才会得到最优解。你现在并不关心这种选择具体是如何得到的,只是假定已经知道了这种选择;
给定可获得的最优解的选择后,确定这次选择会产生哪些子问题,以及如何最好地刻画子问题空间;
证明作为构成原问题最优解的组成部分,每个子问题的解就是它本身的最优解。方法是反证法,考虑加入某个子问题的解不是其自身的最优解,那么就可以从原问题的解中用该子问题的最优解替换掉当前的非最优解,从而得到原问题的一个更优的解,从而与原问题最优解的假设矛盾。
要保持子问题空间尽量简单,只在必要时扩展。

最优子结构的不同体现在两个方面:

原问题的最优解中涉及多少个子问题;
确定最优解使用哪些子问题时,需要考察多少种选择。
子问题图中每个定点对应一个子问题,而需要考察的选择对应关联至子问题顶点的边。

2.无后效性

已经求解的子问题,不会再受到后续决策的影响。

3.子问题重叠

如果有大量的重叠子问题,我们可以用空间将这些子问题的解存储下来,避免重复求解相同的子问题,从而提升效率。


几种dp

1.线性dp

这种dp没什么好说的,就是设计起来比较简单,没什么难度

这个以后再补,现在讲讲我最近学的理解一下这两种dp

2.区间dp

区间dp顾名思义,就是枚举每个区间,来找到所需要的值,区间dp一般的式子都是这样的\(dp[l][r]\),这一看就知道,这个dp式子记录的是\([l,r]\)之间的需要求的价值,一般只需要输出\(dp[1][n]\)就行了,状态转移方程也一般就是比较简单,有的题可能需要加一些附加的条件,都是显而易见的条件了,方程一般是(最大值为例):

\[dp[l][r] = max( dp[l][r] , dp[l][k] + dp[k+1][r]) \]

	for(int len=2;len<=n;len++)//枚举区间的大小
	{
		for(int l=1;l+len-1<=n;l++)//枚举左端点
		{
			int r=l+len-1;//这里直接通过区间大小和左端点直接计算出右端点,省了一些不必要的麻烦
			for(int k=l;k<r;k++)//枚举[l,r],之间的中间值,就是在[i,r]中哪里断开dp
			{
				dp1[l][r]=max(dp1[l][r],dp1[l][k]+dp1[k+1][r]);
			}
		}
	}	

但是要注意一下最后一层循环\(k<r\),因为下面有\(dp[k+1][r]\),防止越界

区间dp还有一个比较重要的知识点——断环为链

断环为链其实很简单,就是在输入的时候\(a[i+n]=a[n]\)这样就行了,这样就能保证数据在\(a[N]\)数组中第\(n\)个数后面就是第\(1\)个数了,成功的变成了一条链

但是断环为链中有一个比较重要的东西,就是枚举\(l\)也就是左端点的时候要枚举到\(n*2\),因为断链之后后半部分中也需要前面的状态去更新后面的状态,所以说每个状态都要被更新到,所以\(l\)左端点就要遍历每个数,又因为我们的\(r\)右端点是根据左端点写的,所以就省了一些不必要的麻烦,这样就完美解决了这个问题

3.树形dp(我的理解比较浅显)

树形dp,顾名思义就是长得很像树的dp,其实就是在树上做\(dp\),和普通的dp没什么两样,但是属树形dp需要先\(dfs\)到叶子节点然后回溯的时候开始dp,这样就能保证了上面的节点修改不会影响到下面(无后效性),然后记录的时候就用链式前向星存储,别忘了\(1\)\(0\)不一定是根节点,我们要找到入度为0的点来让根节点搜索就到这了,累了,改天再写

放个树形dp的模板的(算是)

洛谷P1352没有上司的舞会

#include<iostream>
#include<vector>
using namespace std;
const int N=2e4+5;
int n,m,Ans,l,k,Root; 
int dp[N][2],h[N],f[N],du[N];
vector<int> G[N];

void dfs(int u)
{
	dp[u][1]=h[u];
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		dfs(v);
		dp[u][0]+=max(dp[v][1],dp[v][0]);
		dp[u][1]+=dp[v][0];
	}
}

signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++) 
	cin>>h[i]; 
	for(int i=1;i<n;i++)
	{
		cin>>l>>k;
		G[k].push_back(l);
		du[l]++;
	}
	for(int i=1;i<=n;i++)
	{
		if(!du[i]) 
		{
			Root=i;
			break;
		}
	}
	dfs(Root);
	cout<<max(dp[Root][1],dp[Root][0]);
	return 0; 
} 
posted @ 2022-08-03 21:18  Low_key_smile  阅读(19)  评论(0编辑  收藏  举报
//music