动态规划三
复健
动态规划(三)树形
树形一般思路:从分析子树入手,最优解通常是与子树根节点有关的函数,状态计算就是寻找根节点与子节点以及边权的递推关系
编写代码,通常要,从根到叶,再从叶到根,在合适的时候
没有上司的舞会
https://www.luogu.com.cn/problem/P1352
表示以为根节点的子树,同时不包括的快乐指数
表示以为根节点的子树,同时包括的快乐指数
故
从根节点开始一遍即可
#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;
}
注意到数组,如果两维都开,最后会有一个点,最后不得已改成了这样反而通过了,但是如果遇到大于个节点而且只有一个根节点其余全是叶节点的情况等等,这是不能过的
所以还是采用数组去做比较好
树形背包(有依赖的背包)
https://www.acwing.com/problem/content/10/
表示以为根节点,体积和不超过容量时所获得的最大价值
节点有个子结点,每个子结点可以选也可以不选
我们可以把这个子结点看作组物品,每组物品按照单位体积拆分,有中决策(按单位体积拆分是因为的子孙可能存在体积为的物品,拆分到是因为要预留出的空间装入节点的物品
然后从根到叶进行即可
#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]);
}
树的重心
重心是指将该点删除后,在剩下的各个连通块中点数的最大值最小的点
记录的最大子树的节点数,记录以为根的子树的节点数(包含),那么就是上面部分的节点数,以为重心时,其最大子树节点数就为
#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;
}
树的直径(树的最长路径)
任取一点,从点向下向下搜,返回时收集边的权值
记录从点往下走的最长路径的长度,记录从点往下走的次长路径的长度
表示悬挂在点上的最长路径的长度
但是,其实不需要开设一个数组,每遍历完一个点,及时更新全局变量即可,
#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;
}
树的中心
某点到树上其他点的最远距离最近,即为树的中心
从点往下走的最长长度:,次长长度为
从点往上走的最长长度,自上而下递推,
如果在从点向下走的最长路径上,则
如果不在从向下走的最长路径上,则
#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;
}
积蓄程度
https://www.acwing.com/problem/content/289/
可以利用求树的中心的思想,不必以每个点作源点去做,而是做两遍,一次向上,一次向下
从叶向上递推,获取从各点向下流出的最大流量,从根向下递推,获取从各点向外流出的最大流量(也就是向上加向下的)
向上递推就是先再计算,向下递推就是先计算再
,
求外流量用更新子结点,河道与这条比的下流量为,点的全流量(其他包括的其他子结点以及往上的流量),的上流量
点的全流量就为
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现