复健\(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(\sum f[s][0],f[s][1]),f[u][1]+=\sum 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\)时所获得的最大价值
节点\(u\)有\(i\)个子结点,每个子结点可以选也可以不选
我们可以把这\(i\)个子结点看作\(i\)组物品,每组物品\(s[i]\)按照单位体积拆分,有\(0,1,2,\cdots ,j-v[u]\)中决策(按单位体积拆分是因为\(s[i]\)的子孙可能存在体积为\(1\)的物品,拆分到\(j-v[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\)),那么\(n-sum\)就是\(u\)上面部分的节点数,以\(u\)为重心时,其最大子树节点数就为\(ans=max(size,n-sum)\)
#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]=\sum min(w[i],d[v])\),
求外流量用\(u\)更新子结点\(v\),河道\(u\)与\(v\)这条比的下流量为\(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;
}
标签:include,int,d2,dfs,maxn,动态,规划,d1
From: https://www.cnblogs.com/iwillenter-top1/p/17603312.html