返回顶部

DP总结

DP总结

背包DP

-0/1背包
-完全背包
-多重背包
-分组背包
-依赖背包
-二维背包
-树形背包DP

0/1背包

朴素版

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1010;

//f[i][j]表示前i个物品,体积不超过j时的最大价值
//不选第i个物品时,f[i][j] = f[i-1][j]
//选第i个物品时,f[i][j] = f[i-1][j-v[i]]+w[i],保证j>=v[i] 
int f[maxn][maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值

int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &v[i], &w[i]); 
	
	for(int i=1; i<=n; i++)
	{
		for(int j=0; j<=m; j++)
		{
			f[i][j] = f[i-1][j];
			if(j>=v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
		}
	}
	printf("%d", f[n][m]);

	return 0;
}

滚动数组版

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1010;

int f[2][maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值

int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &v[i], &w[i]); 
	
	for(int i=1; i<=n; i++)
	{
		for(int j=0; j<=m; j++)
		{
			f[i&1][j] = f[(i-1)&1][j];
			if(j>=v[i]) f[i&1][j] = max(f[i&1][j], f[(i-1)&1][j-v[i]] + w[i]);
		}
	}
	printf("%d", f[n&1][m]);

	return 0;

一维

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1010;

int f[maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值

int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &v[i], &w[i]); 
	
	for(int i=1; i<=n; i++)
	{
		for(int j=m; j>=v[i]; j--)
		{ 
			f[j] = max(f[j], f[j-v[i]] + w[i]);
		}
	}
	printf("%d", f[m]);

	return 0;
}

完全背包

image

朴素版
点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1010;

//f[i][j]表示前i个物品,体积不超过j时的最大价值
//f[i][j]=max(f[i-1][j], f[i-1][j], f[i-1][j-v[i]]+w[i], f[i-1][j-2*v[i]]+2*w[i], ....)
int f[maxn][maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值

int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &v[i], &w[i]); 
	
	for(int i=1; i<=n; i++)
	{
		for(int j=0; j<=m; j++)
		{ 
			for(int k=0; k*v[i]<=j; k++)
			{
				f[i][j] = max(f[i][j], f[i-1][j-k*v[i]] + k*w[i]);
			}
		}
	}
	printf("%d", f[n][m]);

	return 0;
}

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1010;

//f[i][j]表示前i个物品,体积不超过j时的最大价值 
//f[i][j] = max(f[i-1][j], f[i][j-v] + w)
int f[maxn][maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值

int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &v[i], &w[i]); 
	
	for(int i=1; i<=n; i++)
	{
		for(int j=0; j<=m; j++)
		{ 
			f[i][j] = f[i-1][j];
			if(j >= v[i]) f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);
		}
	}
	printf("%d", f[n][m]);

	return 0;
}

一维
点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1010;

int f[maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值

int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &v[i], &w[i]); 
	
	for(int i=1; i<=n; i++)
	{
		for(int j=v[i]; j<=m; j++)
		{ 
			f[j] = max(f[j], f[j-v[i]] + w[i]);
		}
	}
	printf("%d", f[m]);

	return 0;
}

多重背包

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 110;

int n = 0, m = 0;	 
int f[maxn] = {};	 
int v[maxn] = {}, w[maxn] = {}, s[maxn] = {};	 

int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &v[i], &w[i], &s[i]);
	}

	for(int i=1; i<=n; i++)
	{
		for(int j=1; j<=s[i]; j++)
		{
			for(int k=m; k>=v[i]; k--)
			{
				f[k] = max(f[k], f[k-v[i]] + w[i]);
			}			
		}
	}
	printf("%d", f[m]);

	return 0;
}

二进制拆分多重背包

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 15000;
const int maxm = 2010;

int n = 0, m = 0;	 
int f[maxm] = {};	 
int v[maxn] = {}, w[maxn] = {}, s[maxn] = {}, cnt = 0;	 

int main()
{	
	int vi = 0, wi = 0, si = 0;
	scanf("%d%d", &n, &m);
	//二进制拆分
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &vi, &wi, &si);
		if(si > m / vi) si = m / vi;
		for(int j=1; j<=si; j<<=1)
		{
			v[++cnt] = j * vi;
			w[cnt] = j * wi;
			si -= j;
		}
		if(si > 0)
		{
			v[++cnt] = si * vi;
			w[cnt] = si * wi;
		}
	}
	
	//0/1背包
	for(int i=1; i<=cnt; i++)
	{
		for(int j=m; j>=v[i]; j--)
		{
			f[j] = max(f[j], f[j-v[i]] + w[i]);
		}	
	}
	printf("%d", f[m]);

	return 0;
}


分组背包

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 40;
const int maxm = 210;

//分组背包
int n = 0, m = 0, t = 0;	 
int v[maxn] = {}, c[maxn] = {};
//g[i][j]表示第i组第j个物品的编号
int g[15][maxn] = {};
//f[i][j]表示前i组物品,体积不超过j的最大价值
int f[15][maxm] = {};

int main()
{	 
	int x = 0;
	scanf("%d%d%d", &m, &n, &t); 
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &v[i], &c[i], &x);
		g[x][++g[x][0]] = i;
	}
	
	for(int i=1; i<=t; i++)
	{
		for(int j=0; j<=m; j++)
		{
			f[i][j] = f[i-1][j];
			for(int k=1; k<=g[i][0]; k++)
			{
				if(j >= v[g[i][k]]) 
				{
					x = g[i][k];
					f[i][j] = max(f[i][j], f[i-1][j-v[x]] + c[x]);	
				}
			}
		}
	}
	printf("%d", f[t][m]);

	return 0;
}

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 40;
const int maxm = 210;

//分组背包
int n = 0, m = 0, t = 0;	 
int v[maxn] = {}, c[maxn] = {}, g[15][maxn] = {};
int f[maxm] = {};

int main()
{	 
	int x = 0;
	scanf("%d%d%d", &m, &n, &t); 
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &v[i], &c[i], &x);
		g[x][++g[x][0]] = i;
	}
	
	for(int i=1; i<=t; i++)
	{
		for(int j=m; j>=0; j--)
		{
			for(int k=1; k<=g[i][0]; k++)
			{
				if(j >= v[g[i][k]]) 
				{
					x = g[i][k];
					f[j] = max(f[j], f[j-v[x]] + c[x]);	
				}
			}
		}
	}
	printf("%d", f[m]);

	return 0;
}

vector数组存最方便,也可用链式前向行

依赖背包 例题Consumer

点击查看代码
	#include <cstdio>
	#include <cstring>
	#include <algorithm>
	using namespace std;	
	int n,w;
	int dp[100005];
	int num[55];// 记录每组物品个数
	int priceOfBox[55];// 盒子价格 
	int price[55][15];// 物品花费 
	int value[55][15];// 物品价值 
	int dp_tmp[100005]; // 临时数组 
	
	int main()
	{
	    while(scanf("%d%d",&n,&w) != EOF) {
	        memset(dp,0,sizeof(dp));
		    for(int i = 1;i <= n;i++) {
		        scanf("%d",&priceOfBox[i]);
		        scanf("%d",&num[i]);
		        for(int j = 1;j <= num[i];j++) {
		            scanf("%d%d",&price[i][j],&value[i][j]);
		        } 
		    }
		    for(int i = 1;i <= n;i++)  {
		        memcpy(dp_tmp,dp,sizeof(dp));
		        // 买不起盒子,打好标记 
		        for(int j = 0;j < priceOfBox[i];j++)
		            dp[j] = -1;
		        for(int j = priceOfBox[i];j <= w;j++)    
		            dp[j] = dp_tmp[j-priceOfBox[i]];
		        for(int j = 1;j <= num[i];j++) {				        	
		            for(int k = w;k >= price[i][j];k--) {
						if(dp[k-price[i][j]] != -1)									            		
		                	dp[k] = max(dp[k],dp[k-price[i][j]] + value[i][j]);
		            }
		        }
		        for(int j = 0;j <= w;j++)    
		            dp[j] = max(dp_tmp[j],dp[j]);
		    }
		    
		    printf("%d\n",dp[w]);
	    }
	   return 0;
	} 

image

image

二维费用背包

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 60;
const int maxm = 410;

//二维费用背包
int n = 0, v = 0, m = 0;	 
int a[maxn] = {}, b[maxn] = {}, c[maxn] = {};
int f[maxm][maxm] = {};

int main()
{	 
	scanf("%d%d%d", &v, &m, &n);
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &a[i], &b[i], &c[i]);
	}
	
	for(int i=1; i<=n; i++)
	{
		for(int j=v; j>=a[i]; j--)
		{
			for(int k=m; k>=b[i]; k--)
			{
				f[j][k] = max(f[j][k], f[j-a[i]][k-b[i]] + c[i]);
			}
		}
	}
	printf("%d", f[v][m]);

	return 0;
}

树形背包DP

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int m,n,w[1005],f[1005][1005];
vector <int> a[1005];
void dp(int fa)
{
	for(int i=0;i<a[fa].size();i++)
	{
		int son=a[fa][i];
		dp(son);
		for(int j=n;j>=0;j--)	
			for(int k=0;k<=j;k++)
				f[fa][j]=max(f[fa][j],f[fa][j-k]+f[son][k]);
	}
	if(fa)
	{
		for(int j=n;j>0;j--)
			f[fa][j]=f[fa][j-1]+w[fa];
	}
}
int main()
{
	cin>>m>>n;
	int x,y;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>w[i];
		a[x].push_back(i);
	}
	dp(0);
	cout<<f[0][n];
	return 0;
}

Vector

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int m,n,w[1005],f[1005][1005];
vector <int> a[1005];
void dp(int fa)
{
	for(int i=0;i<a[fa].size();i++)
	{
		int son=a[fa][i];
		dp(son);
		for(int j=n;j>=0;j--)	
			for(int k=0;k<j;k++)
				f[fa][j]=max(f[fa][j],f[fa][j-k-1]+f[son][k]+w[son]);
	}

}
int main()
{
	cin>>m>>n;
	int x,y;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>w[i];
		a[x].push_back(i);//x->i
	}
	dp(0);
	cout<<f[0][n];
	return 0;
}

链式前向星

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,cnt;
int f[200][200];
struct node{
	int from;
	int to;
	int w;
}edge[20000];
int head[2000];
void dfs(int zi,int gen){
	for(int i=head[zi];i;i=edge[i].from){
		int to=edge[i].to;
		dfs(to,zi);
		for(int j=m;j>=1;j--){
			for(int k=0;k<j;k++){
				f[zi][j]=max(f[zi][j],f[edge[i].to][k]+f[zi][j-k-1]+edge[i].w);
			}
		}
	}
}
void add(int from,int to,int w){
	cnt++;
	edge[cnt].from=head[from];
	edge[cnt].to=to;
	edge[cnt].w=w;
	head[from]=cnt;
}
int main(){
	int from,to,w;
	cin>>n>>m;
	for(int i=1;i<=n-1;i++){
		cin>>from>>to>>w;
		add(from,to,w);
	}
	dfs(1,0);
	cout<<f[1][m];
}

oi-wiki树形背包DP 树形DP

点击查看代码
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
int f[305][305], s[305], n, m;
vector<int> e[305];

int dfs(int u) {
  int p = 1;
  f[u][1] = s[u];
  for (auto v : e[u]) {
    int siz = dfs(v);
    // 注意下面两重循环的上界和下界
    // 只考虑已经合并过的子树,以及选的课程数超过 m+1 的状态没有意义
    for (int i = min(p, m + 1); i; i--)
      for (int j = 1; j <= siz && i + j <= m + 1; j++)
        f[u][i + j] = max(f[u][i + j], f[u][i] + f[v][j]);  // 转移方程
    p += siz;//p表示的是节点数
  }
  return p;
}

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) {
    int k;
    scanf("%d%d", &k, &s[i]);
    e[k].push_back(i);
  }
  dfs(0);
  printf("%d", f[0][m + 1]);
  return 0;
}

注意事项:dfs中循环,第一层倒序n/m->1(到0或1没影响)!!!注意第二层如果以零为起点,那么(应为要预留出一个空间给根节点所以j+1-1)k<=j,如果以1为起点或其他则看k<=j-1,还有如果最后没有初始化,而是写在了dfs的两层循环中,(以1为根为例)即dp[fa][j]=max(dp[fa][j],dp[fa][j-根的体积-k]+f[son][k]+父亲的权值)额外注意vector存图父亲的权值指向的是儿子还是自身


小结:注意背包DP特征如体积.价值.空间(给定一个限制值求另外一个值的最优)
还有初始化的问题max0x3f,min-0x3f,可行性-1
f[0]=0之类的,还有至多同一个dp[i][j]=max(dp[i][j],dp[i-1][j-v]+w)
至少一个初始化成级小dp[i][j]=max(dp[i][j],dp[i-1][j-v]+w,dp[i][j-v]+w)


线性DP

点击查看代码
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
		{
			if(a[j]<a[i])
			{
				f[i]=max(f[i],f[j]+1);
			}
		}
		ans=max(ans,f[i]);//注意,如果放for循环里面如果i=1时最大会被忽略,所以,如果想放里面,可以改为就j<=i
	}

特征:线性 成一定规律(如递增递减)

导弹拦截问题简单版

  • 偏序集
    n 偏序是在集合X上的二元关系≤(这只是个抽象符号,不
    是“小于或等于”),它满足自反性、反对称性和传递性。
    即,对于X中的任意元素a,b和c,有:
    –自反性:a≤a;
    –反对称性:如果a≤b且b≤a,则有a=b;
    –传递性:如果a≤b且b≤c,则a≤c 。
    带有偏序关系的集合称为偏序集。
    令(X,≤)是一个偏序集,对于集合中的两个元素a、b,如
    果有a≤b或者b≤a,则称a和b是可比的,否则a和b不可比。
    一个反链A是X的一个子集,它的任意两个元素都不能进
    行比较。
    一个链C是X的一个子集,它的任意两个元素都可比
    -定理
    在X中,对于元素a,如果任意元素b,都有a≤b,则称a为极小元。
    定理1:令(X,≤)是一个有限偏序集,并令r是其最大链的大小。则X
    可以被划分成r个但不能再少的反链。
    其对偶定理称为Dilworth定理:
    令(X,≤)是一个有限偏序集,并令m是反链的最大的大小。则X可以
    被划分成m个但不能再少的链。
    拦截导弹 要求最少的覆盖,按照Dilworth定理:最少链划分= 最长反链长

最长公共子串问题

定义:字串是一个字符串中连续的一段,公共子串即为几个字符串都含有的子串.

\(\ dp[i][j]=\begin{cases}dp[i-1][j-1]+1(s[x]==s[y])\\0 (s[x]!=s[y])\end{cases}\)

最长公共子序列问题

定义:字序列是一个字符串中有序的一段,即序列中的每个数在原序列内都从左到右排列,公共子序列即为几个字符串都含有的子序列.

\(\ dp[i][j]=\begin{cases}dp[i-1][j-1]+1(s[x]==s[y])\\max(dp[i][j-1],dp[i-1][j]) (s[x]!=s[y])\end{cases}\)

点击查看代码
for (int i = 1; i <= n; i ++ )
{
    for (int j = 1; j <= n; j ++ )
    {
        f[i][j] = f[i - 1][j];
        if (a[i] == b[j])
        {
            int maxv = 1;
            for (int k = 1; k < j; k ++ )
                if (a[i] > b[k])
                    maxv = max(maxv, f[i - 1][k] + 1);
            f[i][j] = max(f[i][j], maxv);
        }
    }
}

区间DP

特征:一个区间,如字符串

点击查看代码
	for (len = 2; len <= n; len++)
	  for (i = 1; i <= 2 * n - 1 - len; i++) {
		int j = len + i - 1;
		for (k = i; k < j; k++)
		  f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]) + sum[j] - sum[i - 1];//以石子合并为例
	  }

石子合并优化

image

image

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 4010;

int n = 0;
//f[i][j]表示i~j堆这个区间的总分最多
//1、f[i][j]由f[i+1][j]与a[i]合并而来
//2、f[i][j]由f[i][j-1]与a[j]合并而来
int f[maxn][maxn] = {};
//s表示a的前缀和
int a[maxn] = {}, s[maxn] = {};

int main()
{	
	scanf("%d", &n);
	for(int i=1; i<=n; i++) 
	{
		scanf("%d", &a[i]);
		a[n+i] = a[i]; 
	}
	for(int i=1; i<=n*2; i++) s[i] = s[i-1] + a[i];	
	
	for(int len=2; len<=n; len++)	//阶段,区间长度
	{
		for(int i=1; i<=2*n-len+1; i++)
		{
			int j = i + len - 1;
			f[i][j] = max(f[i+1][j], f[i][j-1]) + s[j] - s[i-1];
		}
	}
	int ans = 0;
	for(int i=1; i<=n; i++)
	{
		ans = max(ans, f[i][n+i-1]);
	}
	printf("%d\n", ans);

	return 0;
} 

石子合并最小值

坐标DP

特征:形如其名,一个图上的DP,有时需要自己构建图

树形DP

特征:一般关系成树形,二叉树,森林树,如何构建树
如森林树转二叉树就是第一个作为左儿子,第二个右儿子,第三个作为右儿子的右儿子,类推

posted @ 2024-02-17 16:54  wlesq  阅读(11)  评论(0编辑  收藏  举报