【董晓算法】动态规划之背包DP与树形DP

前言:

本系列是学习了董晓老师所讲的知识点做的笔记

董晓算法的个人空间-董晓算法个人主页-哔哩哔哩视频 (bilibili.com)

 动态规划系列(还没学完)

【董晓算法】动态规划之线性DP问题-CSDN博客

【董晓算法】动态规划之背包DP问题(2024.5.11)-CSDN博客

背包DP

背包问题方案数

f[i]表示背包容量为i时能装入物品的最大价值,c[i]表示营包容量为i时最优选法的方案数

for (int i = 0; i <= m; i++) c[i] = 1;//背包里不装入物品也是一种方案
	for (int i = 1; i <= n; i++)
	{
		scanf("%d%d", &v, &w);
		for (int j = m; j >= v; j--) {
			if (f[j - v] + w > f[j]) {//容量从j-v增加到j,只是多装入一件物品,没有改变方案数c[j-v]所以c[j] = c[j-v]
				f[j] = f[j - v] + w;
				c[j] = c[j - v];
			}
			else
				//不装入新物品,容量j已有的方案数为c[j]; 
				//若装入新物品,容量j对应的方案数为c[j - v]
				//两种情况,方案显然不同,所以取和。为防止爆掉int,对大数取余
				c[j] = (c[j] + c[j - v]) % mod;
		}
	}
	printf("%d\n", c[m]);

背包问题求具体方案

题目要求输出字典序最小的方案,假设存在一个包含第1个物品的最优解,为了确保字典序最小那么我们必然要选第1个。那么问题就转化成从2~N这些物品中找到最优解。

首先,我们从后向前遍历物品,让最优解落在f[1][m]中;

然后,我们从f[1][m]开始搜索字典序最小的路径方案。

状态定义:f[i][j]表示从第i个物品到最后一个物品装入容量为j的背包的最优解。

状态转移:f[i][j]= max(f[i+1][j],f[i+1][j-v[i]]+ w[i])

  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++){  
    f[i][j]=f[i+1][j];
    p[i][j]=j;              //记录路径列 
    if(j>=v[i])
      f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
    if(j>=v[i] && f[i][j]==f[i+1][j-v[i]]+w[i])
      p[i][j]=j-v[i];//记录路径列
  }
 int j=m;
  for(int i=1; i<=n; i++){//找路
    if(p[i][j]<j){
      printf("%d ",i);
      j=p[i][j];
    }

树形DP

一般思路:

      线形DP子结构是一条线段,树形DP子结构是一颗子树。从分析子树入手,最优解通常是与子树根节点u有关的函数,状态计算就是寻找根点与子节点以及边权的递推关系。

      编写代码,通常要dfs,从根到叶一路深搜,再从叶到根做后序DP,每次用其子树的f值更新当前节点的f值,兜了一圈回到根节点根节点的f值就是全局最优解。

没有上司的舞会

P1352 没有上司的舞会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

存储代码

vector<int>a[N];
 cin >> x >> y;
a[y].push_back(x);

核心代码

void dfs(int u) {//深搜节点+后序DP
    f[u][1] = w[u];//选u的快乐指数
    for (int v : a[u]) {
        dfs(v);
        f[u][0] += max(f[v][0], f[v][1]);//不选择根节点的话,他的子节点可选可不选(根据题意)
        f[u][1] += f[v][0];//选择根节点,子节点不可以选(根据题意)
    }

 总代码

#include<iostream>
#include<vector>
using namespace std;
const int N =6010;
int n;
int w[N];
vector<int> a[N];
bool fa[N];
int f[N][2];

void dfs(int u) {//深搜节点+后序DP
    f[u][1] = w[u];//选u的快乐指数
    for (int v : a[u]) {
        dfs(v);
        f[u][0] += max(f[v][0], f[v][1]);//不选择根节点的话,他的子节点可选可不选(根据题意)
        f[u][1] += f[v][0];//选择根节点,子节点不可以选(根据题意)
    }


}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)  
        cin >> w[i];//存储快乐指数
    for (int i = 0; i < n - 1; i++) {
        int x, y;
        cin >> x >> y;
        //把y的邻接点x存入数组a
         //y的邻接点个数存入b数组中去
        a[y].push_back(x);
        fa[x] = true;//x有父亲节点
    }
    int root = 1;
    while (fa[root])  root++;//找根节点 如果他有父亲节点的话就说明他不是根节点
    dfs(root);
    cout << max(f[root][0], f[root][1]);//选择根节点或者不选择根节点

}

树形背包

 对01背包进行的改造:

f[i]表示背包容量为i时能装入物品的最大价值
c[i]表示营包容量为i时最优选法的方案数 

void dfs(int u){
  for(int i=v[u];i<=m;i++) f[u][i]=w[u];//如果选u的话,体积不能够小于v[u]
  for(int i=0;i<b[u];i++){  //子节点 
    int s=a[u][i];
    dfs(s);//递归儿子
    for(int j=V;j>=v[u];j--)    //体积
      for(int k=0;k<=j-v[u];k++)//决策
        f[u][j]=max(f[u][j],f[u][j-k]+f[s][k]);
  }
}

树的重心

重心的定义

重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个结点被称为树的重心。

要求:

找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

代码
int dfs(int u)
{
	vis[u] = true;
	int size = 0;//记录u的最大子树的结点数;
	int sum = 1;//记录以u为根的子树的结点数:
	for (int i = h[u]; i != -1; i = ne[i]) {//i是边的编号
		int j = e[i];//j是u的邻接点
		if (vis[j]) continue;//避免向上搜索
		int s = dfs(j);//s是以j为根的字数的节点数
		size = max(size, s);//记录u的最大子树的结点数
		sum += s;//累加u的各个子树的结点数
	}
	ans = min(ans, max(size, n - sum)); //n-sum u上面的部分的结点数:
	return sum;
}

树的最长路径

找到一条路径,使得路径两端点的距离最远。

思路:找到从根节点向下走的最长路径和次长路径加起来

int dfs(int u)
{
	vis[u] = true;
	int d1 = 0;//最长
	int d2 = 0;//次长
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];//j是u的临界点
		if (vis[j]) continue;
		int d = dfs(j) + w[i];//从u经过j点往下走的最大长度
		if (d >= d1)//比最长还要长
		{
			d2 = d1;
			d1 = d;
		}
		else if (d > d2) {
			d2 = d;
		}
	}
	ans = max(ans, d1 + d2);
	return ans;
}

树的中心

定义

该点到树中其他点的最远距离最近

思路

分类处理

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值