动态规划三

复健Day4

动态规划(三)树形DP

树形DP一般思路:从分析子树入手,最优解通常是与子树根节点u有关的函数,状态计算就是寻找根节点与子节点以及边权的递推关系

编写代码,通常要DFS,从根到叶,再从叶到根,在合适的时候DP

1.没有上司的舞会

https://www.luogu.com.cn/problem/P1352

f[u][0]表示以u为根节点的子树,同时不包括u的快乐指数

f[u][1]表示以u为根节点的子树,同时包括u的快乐指数

f[u][0]+=max(f[s][0],f[s][1]),f[u][1]+=f[s][0]

从根节点u开始dfs一遍即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 6010
using namespace std;

int f[maxn][2];
int w[maxn];
int s[maxn][5010],sz[maxn];
bool fa[maxn];

void dfs(int u)
{
	f[u][1]=w[u];
	for(int i=1;i<=sz[u];i++)
	{
		int v=s[u][i];
		dfs(v);
		f[u][0]+=max(f[v][0],f[v][1]);
		f[u][1]+=f[v][0];
	}
}

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		s[v][++sz[v]]=u;
		fa[u]=true;//用于找根节点
	}
	int rt=1;
	while(fa[rt]) rt++;
	dfs(rt);
	printf("%d\n",max(f[rt][0],f[rt][1]));
	return 0;
}

注意到s[maxn][5010]数组,如果两维都开6010,最后会有一个点MLE,最后不得已改成了这样反而通过了,但是如果遇到大于5010个节点而且只有一个根节点其余全是叶节点的情况等等,这是不能过的

所以还是采用vector数组去做比较好

2.树形背包(有依赖的背包)

https://www.acwing.com/problem/content/10/

f[u][j]表示以u为根节点,体积和不超过容量j时所获得的最大价值

节点ui个子结点,每个子结点可以选也可以不选

我们可以把这i个子结点看作i组物品,每组物品s[i]按照单位体积拆分,有0,1,2,,jv[u]中决策(按单位体积拆分是因为s[i]的子孙可能存在体积为1的物品,拆分到jv[u]是因为要预留出v[u]的空间装入节点u的物品

然后从根到叶进行dfs即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;

int f[maxn][maxn];
int v[maxn],w[maxn];
int s[maxn][maxn],sz[maxn];
int n,V;
int rt;

void dfs(int u)
{
	for(int i=v[u];i<=V;i++) f[u][i]=w[u];
	for(int i=1;i<=sz[u];i++)
	{
		int son=s[u][i];
		dfs(son);
		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[son][k]);
			}
		}
	}
}

int main()
{
	cin>>n>>V;
	for(int i=1;i<=n;i++)
	{
		int p;
		cin>>v[i]>>w[i]>>p;
		if(p==-1)
		{
			rt=i;
			continue;
		}
		else s[p][++sz[p]]=i;
	}
	dfs(rt);
	printf("%d\n",f[rt][V]);
}

3.树的重心

重心是指将该点删除后,在剩下的各个连通块中点数的最大值最小的点

size记录u的最大子树的节点数,sum记录以u为根的子树的节点数(包含u),那么nsum就是u上面部分的节点数,以u为重心时,其最大子树节点数就为ans=max(size,nsum)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;

int head[maxn<<1],tot;
int ans,n;
int vis[maxn];

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

int dfs(int rt)
{
	vis[rt]=1;
	int size=0,sum=1;
	for(int i=head[rt];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(vis[v]) continue;
		int s=dfs(v);
		size=max(size,s);
		sum+=s;
	}
	ans=min(ans,max(size,n-sum));
	return sum;
}

int main()
{
	memset(head,-1,sizeof(head));
	cin>>n;
	ans=0x7fffffff;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	dfs(1);
	printf("%d\n",ans);//ans存储最大子树节点数的最小值
	return 0;
}

4.树的直径(树的最长路径)

任取一点u,从u点向下向下搜,返回时收集边的权值

d1记录从u点往下走的最长路径的长度,d2记录从u点往下走的次长路径的长度

d[u]=d1+d2表示悬挂在u点上的最长路径的长度

但是,其实不需要开设一个d[]数组,每遍历完一个点,及时更新全局变量ans即可,ans=max(ans,d1+d2)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 100010
using namespace std;

int head[maxn],tot;
int vis[maxn];
int ans;

struct Edge
{
	int v,dis,nxt;
	Edge(){}
	Edge(int v,int dis,int nxt):v(v),dis(dis),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}

int dfs(int u)
{
	vis[u]=1;
	int d1=0,d2=0;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int son=ed[i].v;
		if(vis[son]) continue;
		int d=dfs(son)+ed[i].dis;
		if(d>=d1) d2=d1,d1=d;
		else if(d>d2) d2=d;
	}
	ans=max(ans,d1+d2);
	return d1;
}

int main()
{
	int n;
	cin>>n;
	memset(head,-1,sizeof(head));
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);add(v,u,w);
	}
	dfs(1);
	printf("%d\n",ans);
	return 0;
}

5.树的中心

某点到树上其他点的最远距离最近,即为树的中心

u点往下走的最长长度:d1[u]=d1[v]+w[i],次长长度为d2[u]=d2[v]+w[i]

u点往上走的最长长度,自上而下递推,

如果v在从u点向下走的最长路径上,则up[v]=w[i]+max(up[u],d2[u])

如果v不在从u向下走的最长路径上,则up[v]=w[i]+max(up[u],d1[u])

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 10010
using namespace std;

int head[maxn],tot;

struct Edge
{
	int v,dis,nxt;
	Edge(){}
	Edge(int v,int dis,int nxt):v(v),dis(dis),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}

int d1[maxn],d2[maxn],up[maxn];
int p1[maxn];//记录从u往下走时的最长路径经过哪个点

int dfs_d(int u,int fa)
{
	d1[u]=0,d2[u]=0;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v==fa) continue;
		int d=dfs_d(v,u)+ed[i].dis;
		if(d>=d1[u]) d2[u]=d1[u],d1[u]=d,p1[u]=v;
		else if(d>d2[u]) d2[u]=d;
	}
	return d1[u];
}

void dfs_u(int u,int fa)
{
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v==fa) continue;
		if(p1[u]==v) up[v]=ed[i].dis+max(up[u],d2[u]);
		else up[v]=ed[i].dis+max(up[u],d1[u]);
		dfs_u(v,u);
	}
}

int main()
{
	int n;
	memset(head,-1,sizeof(head));
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,w);
	}
	dfs_d(1,-1);
	dfs_u(1,-1);
	int res=0x7fffffff;
	for(int i=1;i<=n;i++) res=min(res,max(up[i],d1[i]));
	printf("%d\n",res);
	return 0;
}

6.积蓄程度

https://www.acwing.com/problem/content/289/

可以利用求树的中心的思想,不必以每个点作源点去做dfs,而是做两遍dfs,一次向上,一次向下

从叶向上递推,获取从各点向下流出的最大流量d[i],从根向下递推,获取从各点向外流出的最大流量f[i](也就是向上加向下的)

向上递推就是先dfs再计算,向下递推就是先计算再dfs

d[u]=min(w[i],d[v]),

求外流量用u更新子结点v,河道uv这条比的下流量为D=min(w[i],d[v])u点的全流量f[u]=D+(其他包括u的其他子结点以及u往上的流量),v的上流量=min(w[i],f[u]D)

v点的全流量就为f[v]=d[v]+min(w[i],f[u]D)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 200010
#define inf 0x7fffffff
using namespace std;

int head[maxn],tot;
int deg[maxn];
int d[maxn],f[maxn];

struct Edge
{
	int v,dis,nxt;
	Edge(){}
	Edge(int v,int dis,int nxt):v(v),dis(dis),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}

int dfs_d(int u,int fa)
{
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v==fa) continue;
		int s=dfs_d(v,u);//返回d[v]
		d[u]+=min(ed[i].dis,s);
	}
	if(deg[u]==1) return inf;//特判叶的情况
	return d[u];
}

void dfs_f(int u,int fa)
{
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v==fa) continue;
		if(deg[u]==1) f[v]=d[v]+ed[i].dis;//特判叶的情况
		else f[v]=d[v]+min(ed[i].dis,f[u]-min(ed[i].dis,d[v]));
		dfs_f(v,u);
	}
}

int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		cin>>n;
		memset(head,-1,sizeof(head));
		memset(d,0,sizeof(d));
		memset(f,0,sizeof(f));
		memset(deg,0,sizeof(deg));
		for(int i=1;i<n;i++)
		{
			int u,v,w;
			cin>>u>>v>>w;
			add(u,v,w);add(v,u,w);
			deg[u]++;
			deg[v]++;
		}
		dfs_d(1,-1);
		f[1]=d[1];//根只有向下的流量
		dfs_f(1,-1);
		int ans=0;
		for(int i=1;i<=n;i++) ans=max(ans,f[i]);
		printf("%d\n",ans);
	}
	return 0;
}

posted on   dolires  阅读(4)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示