当前位置: 首页 > news >正文

树形DP(树形背包+换根DP)

树形DP

  1. 没有上司的舞会

家常便饭了,写了好几遍,没啥好说的,正常独立集问题。

int head[B];
int cnt;
struct node
{
	int v,nxt;
}e[B<<1];
void modify(int u,int v)
{
	e[++cnt].nxt=head[u];
	e[cnt].v=v;
	head[u]=cnt;
}
int a[B];
int f[B][5];
int n;
void dfs(int u,int pre)
{
	for (int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].v;
		if (v==pre) continue;
		dfs(v,u);
		f[u][1]+=f[v][0];
		f[u][0]+=max(f[v][0],f[v][1]);
	}
}
void work()
{
	cin>>n;
	for (int i=1;i<=n;i++) f[i][1]=read();
	for (int i=1;i<n;i++)
	{
		int v=read(),u=read();
		modify(u,v);
		modify(v,u); 
	}
	dfs(1,0);
	cout<<max(f[1][0],f[1][1]);
}

  1. 选课

树上背包问题。

说说自己犯的错误

  • 式子推得太慢
  • 滚动数组特别容易记混状态,尤其是背包,因为是从大到小更新,如果在枚举到大数的时候发生转移却用的小的,那么更新的答案必然是错误的,这个问题我调了一下午,就是滚动数组的错误,一定切记,对于逆序的滚动数组问题,一定好想好当前所需状态是否已经更新,而不是借用上一维的状态,这种错误最容易犯在背包上!!
  • 这个题目优化点在于,用一个虚拟点来连接森林,这样就不用再写一遍了
#include<bits/stdc++.h>
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int B=1e6+10;
const int inf=0x3f3f3f3f;
int T;
int n,m;
int f[309][309];
int dp[309][309];
int a[B];
int head[B];
int cnt;
struct node
{
	int v,nxt;
}e[B<<1];
void modify(int u,int v)
{
	e[++cnt].nxt=head[u];
	e[cnt].v=v;
	head[u]=cnt;
}
int fa[B];
int vis[B];
void dfs(int u,int pre)
{
	f[u][1]=a[u];
	for (int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].v;
		if (v==pre) continue;
		dfs(v,u);
		
	}
	for (int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].v;
		if (v==pre) continue; 
		for (int j=m;j>=1;j--)
		{
			for (int k=1;k<=j;k++)
				dp[u][j]=max(dp[u][j],dp[u][j-k]+f[v][k]);
			
			//f[u][j]=dp[u][j-1]+a[u];//因为我们的j是倒序枚举,先更新大的在更新小的,那么dp[u][j-1]并不是当前已经对v
			//点进行操作完毕的 dp,而是上一维度 
		}
		for (int j=1;j<=m;j++) f[u][j]=dp[u][j-1]+a[u];//放在外面就是正确的,这是因为用的是当前维度下的dp,滚动数组容易反的错误 
	}
} 
int ans[B];
int num;
int F[B];
void work()
{
	cin>>n>>m;
	for (int i=1;i<=n;i++)
	{
		fa[i]=read();
		a[i]=read();
		modify(fa[i],i);
		modify(i,fa[i]);
	}
	for (int i=1;i<=n;i++)
	{
		if (!fa[i])
		{
			dfs(i,0);
			ans[++num]=i;
		}
	}
	for (int i=1;i<=num;i++)
	{
		for (int j=m;j>=1;j--)
		{
			int x=ans[i];
			for (int k=0;k<=min(m,j);k++)
				F[j]=max(F[j],F[j-k]+f[x][k]);
		}	
	}
	cout<<F[m]; 
}
int main()
{
	T=1;
	while (T--) work();
	return 0;
}
  1. 积蓄程度

题面:见ACwing287

思路

换根DP的常规思路

  • 有下往上递推一遍
  • 再由上往下递推一遍

我们先来想如果根节点固定的时候如何做,比较容易的DP,其实也就是递推,没有任何决策问题。

d [ u ] d[u] d[u] 表示以 u u u 为根节点的最小流量。通过题目可以知道,一条路径上的流量取决于最小流量边,所以这是一个维护最小值的问题。

转移有
d [ u ] = ∑ min ⁡ { d [ v ] , w } d[u]=\sum \min\{d[v],w\} d[u]=min{d[v],w}
再来考虑如何换根。

先来考虑由根节点换到儿子会发生什么变化

图

我们会发现, d [ 1 ] d[1] d[1] 中需要去掉 d [ 2 ] d[2] d[2] 做出的贡献,即 d [ 1 ] − min ⁡ { d [ 2 ] , w } d[1]-\min\{d[2],w\} d[1]min{d[2],w}

d [ 2 ] d[2] d[2] 需要加入 d [ 1 ] d[1] d[1] 的贡献,即 d [ 2 ] + min ⁡ { d [ 1 ] , w } d[2]+\min\{d[1],w\} d[2]+min{d[1],w}

我来思考,如果是在树中间换根怎么办,我们发现,父亲节点还需要同时加入父亲父亲的节点情况。

在这里插入图片描述

比如绿色部分,可是这条边比较难表示,不容易找到 w w w,如果我们看成 2 2 2 节点就是根节点,不再是父亲节点的时候,此时的值刚好就是 2 2 2 当根的值,我们用 f [ 2 ] f[2] f[2] 表示 2 2 2 为根的最大值。那么在转移的时候我们就不需要考虑由父亲父亲的情况。转移由
f [ v ] = d [ v ] + min ⁡ { w , f [ u ] − min ⁡ { w , d [ v ] } } f[v]=d[v]+\min\{w,f[u]-\min\{w,d[v]\}\} f[v]=d[v]+min{w,f[u]min{w,d[v]}}

对于度数只为1的还需要特别注意,度数唯一不一定是叶子结点,也可能是根节点,当换根时,根节点变成叶子结点, f [ u ] − min ⁡ { w , d [ v ] } f[u]-\min\{w,d[v]\} f[u]min{w,d[v]} 变成 0 0 0 ,实际上应该直接加入边值 w w w,所以需要特别判断。

换根时候的注意点

  • 必须一直保持根和儿子换,而不是父亲和儿子换。
  • 根节点和叶子结点要注意
#include<bits/stdc++.h>
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int B=1e6+10;
const int inf=0x3f3f3f3f;
int T;
int n;
int head[B],cnt;
struct node
{
	int v,nxt,w;
}e[B<<1];
void modify(int u,int v,int w)
{
	e[++cnt].nxt=head[u];
	e[cnt].v=v;
	e[cnt].w=w;
	head[u]=cnt;
}
int f[B];
int d[B];
int du[B];
void dfs1(int u,int pre)
{
	for (int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].v;
		int w=e[i].w;
		if (v==pre) continue;
		dfs1(v,u);
		if (du[v]!=1) d[u]+=min(d[v],w);
		else d[u]+=w;
	}
} 
void dfs2(int u,int pre)
{
	for (int i=head[u];i;i=e[i].nxt)
	{
		int w=e[i].w;
		int v=e[i].v;
		if (v==pre) continue;
		if (du[u]==1) f[v]=d[v]+w;
		else f[v]=d[v]+min(w,f[u]-min(d[v],w));
		dfs2(v,u);
	}
}
void work()
{
	cin>>n;
	cnt=0;
	memset(head,0,sizeof(head));
	for (int i=1;i<=n;i++)
	{
		f[i]=0;
		d[i]=0;
		du[i]=0;
	}
	for (int i=1;i<n;i++)
	{
		int u=read(),v=read(),w=read();
		modify(u,v,w); du[u]++; du[v]++;
		modify(v,u,w);
	}
	dfs1(1,0); f[1]=d[1];
	dfs2(1,0);
	int ans=0;
	for (int i=1;i<=n;i++) ans=max(ans,f[i]);
	cout<<ans<<"\n";
}
int main()
{
	T=read();
	while (T--) work();
	return 0;
}
/*
1
3
1 2 1
2 3 1
*/ 

相关文章:

  • 交流异步电动机PI双闭环SVPWM矢量控制Simulink
  • 【Redis原理】底层数据结构 五种数据类型
  • 分布式服务监控
  • 使用大语言模型对接OA系统,实现会议室预定功能
  • 【SpringBoot】✈️本地集成支付宝支付功能
  • Redhat及其衍生系统安装python
  • 如何使用wolfsll库生成证书?
  • Xcode如何高效的一键重命名某个关键字
  • SOME/IP--协议英文原文讲解12(完结)
  • 【Git版本控制器】第四弹——分支管理,合并冲突,--no-ff,git stash
  • 大语言模型(LLM)提示词(Prompt)高阶撰写指南
  • 《论面向对象的建模及应用》审题技巧 - 系统架构设计师
  • Onvif协议NVR开发方案指南
  • SQL笔记#数据更新
  • [AI] [ComfyUI]理解ComyUI的基本原理及其图像生成技术
  • 深度学习技术全景图:从基础架构到工业落地的超级进化指南
  • flex布局自定义一行几栏,靠左对齐===grid布局
  • 三维空间中直线的多种表示方法
  • Python 基本语法的详细解释
  • 第1章大型互联网公司的基础架构——1.9 LSM Tree
  • 德清做网站/百度人工客服在线咨询电话
  • 外贸网站推广教程/cpu优化软件
  • 程序员和做网站那个好找工作/沈阳百度seo关键词排名优化软件
  • 网站后台管理的超级链接怎么做/哈尔滨网站优化流程
  • 手机微信可以做网站吗/百度云搜索引擎入口官方
  • 温州网站建设企业/旅行网站排名