WEEK_1(树形dp)

时隔多日,俺又回来啦!!!

之前一段时间看到代码有点犯恶。。。不过现在没事儿了,所以捡起信心和勇气,继续写下去

最近开始准备蓝桥杯了,所以写点相关的题目吧

不出所料。。刚写一道就挡住了-_--------行,我还是多学一点吧。。。

看了整整一周,终于看明白了(。。。嚎啕大哭。。。)

下面整理一下蒟蒻的思路:

树的直径

给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。

现在请你找到树中的一条最长路径。

换句话说,要找到一条路径,使得使得路径两端的点的距离最远。

注意:路径中可以只包含一个点。

输入格式
第一行包含整数 n。

接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。

输出格式
输出一个整数,表示树的最长路径的长度。

数据范围
1≤n≤10000,
1≤ai,bi≤n,
−105≤ci≤105
输入样例:
6
5 1 6
1 4 5
6 3 9
2 6 8
6 1 7
输出样例:
22

思路:首先存的图是双向的,因此用链式前向星浅浅存一个双向图

于是构建了下面这个图,然后dfs开搜

58cc7ae0f1d948dbb4e8183b99773d4a.png

可以看出,当搜到经过6号点的路径时,会有以下三种走法:(以2为起点)

1、2—6:直接进行递归以6为根节点的子树,找出以6为根子树最长的路径长度max1即可

2、2—6—3:在处理第一种情况时找出路径长度的次大值max2,而max1+max2即为所求

3、2—6—1:此情况为节点6的第一和第二种情况,让6号节点处理

嘿嘿,所以就有了下面的状态转移方程:

20946d1981e047a5ab0349fed209438e.png

 放代码啦:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
int head[N],cnt;
int ans; 
int f[N];
struct ee
{
	int to,from,val;
}edge[N<<1];
void add(int u,int v,int w)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].from=head[u];
	head[u]=cnt;
}//链式前向星建图 
int dfs(int root,int fa)
{//自下而上遍历 
	int max1=0,max2=0;
	for(int i=head[root];i;i=edge[i].from)
	{
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		int d=dfs(v,root)+w;
		if(d>max1)
		{//传递 
			max2=max1;
			max1=d;
		}
		else if(d>max2)
		max2=d;//传递 
	}
	ans=max(ans,max1+max2);
	f[root]=max1;
	return f[root];
}
int main()
{
	cin>>n;
	for(int i=1;i<=n-1;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);//双向建边 
		add(v,u,w);
	}
	dfs(1,0);//从根节点开始搜 
	cout<<ans<<endl;
}

树的直径plus(输出路径)

如何找到最长路径呢?

可以设一个pos,代表最长链的根节点,在记录时,可以记录每个点到最底端的最长和次长路径上的下一个节点,这样就可以还原出经过pos节点的最长链的路径,将两边合并即可得到最长链的路径

3e42deaa148e4136a7844219aebf3d50.png

放代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
int cnt,head[N]; 
int next[N][2];
int ans;
int f[N];
int pos;
struct ee
{
	int to,from,val;
}edge[N<<1];
void add(int u,int v,int w)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
int dfs(int root,int fa)
{//由下向上遍历 
	int max1=0,max2=0;
	for(int i=head[root];i;i=edge[i].from)
	{
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		int d=dfs(v,root)+w;
		if(d>max1)
		{
			max2=max1;
			max1=d;
			next[root][1]=next[root][0];
			next[root][0]=v;
		}//next[root][0]为最长链的下一个点 
		else if(d>max2)
		{
			max2=d;
			next[root][1]=v;
		}
	}
	if(ans<max1+max2)
	{
		ans=max1+max2;
		pos=root;
	}
	//若有更长链,则更改ans和pos
	//pos两边路径成为最长链和次长链 
	f[root]=max1;
	return f[root];
}
int main()
{
	cin>>n;
	for(int i=1;i<=n-1;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,w);
	}
	dfs(1,0);
	cout<<ans<<endl;
	int x=next[pos][0];
	stack<int> st;
	while(x)
	{
		st.push(x);
		x=next[x][0];
	}
	while(st.size())
	{
		cout<<st.top()<<" ";
		st.pop();
	}
	cout<<pos<<" ";
	x=next[pos][1];
	while(x)
	{
		cout<<x<<" ";
		x=next[x][1];
	}
	return 0;
}

树的中心(换根dp)

给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。

请你在树中找到一个点,使得该点到树中其他结点的最远距离最近

输入格式
第一行包含整数 n。

接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。

输出格式
输出一个整数,表示所求点到树中其他结点的最远距离。

数据范围
1≤n≤10000,
1≤ai,bi≤n,
1≤ci≤105
输入样例:
5
2 1 1
3 2 1
4 3 1
5 1 1
输出样例:
2

 知道了树的中心的定义,于是有了以下思路:(很巧妙!!!)

首先把任意节点当作根节点。

对于一个节点来说,到其他节点可以有下面几种情况:

        1、从该节点向下走,到底端的最远距离为up[x]

        2、从该节点往上走,到其他节点的最远距离为down[x]

这两种情况取max得到的是到其他节点的最远距离

对于第一种情况:

直接往下递归维护最大值,根据子节点传回来的答案更新父节点

int dfsd(int root,int fa)
{//从上往下递归,在回溯的时候由下往上更新
	for(int i=head[root];i;i=edge[i].from)
	{
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		int d=dfsd(v,root);
		if(d+w>d1[root])
		{
			d2[root]=d1[root];
			d1[root]=d+w;
			next[root]=v;
		}
		else if(d+w>d2[root])
		d2[root]=d+w;
	}
	return d1[root];
}

对于第二种情况:

从上向下遍历所有点,对于节点u,遍历它的所有子节点v:

  • 如果v不在节点u的底端的最长距离路径上,就取到底端距离的最大值和up[u]的最大值加上u到v的距离得到up[v]
  • 否则就用次长距离d2[u]和up[u]的最大值加上u到v的距离得到up[v]
  • 一直递归到子节点v

代码:

int dfsup(int root,int fa)
{
	for(int i=head[root];i;i=edge[i].from)
	{
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		if(next[root]==v)
		up[v]=max(up[v],max(d2[root],up[root])+w);
		else
		up[v]=max(up[v],max(d1[root],up[root])+w);
		dfsup(v,root);
	}
}

对于一些细节:

假设当前点为 x,其父节点为 fa。
对于点x先往上走,到其他节点的最远距离 up[x] 需要由父节点fa的该状态 up[fa] 来更新
fa先往上走,到其他节点的最远距离up[fa] fa往下走到底端,且不经过当前点x的最远距离max + 边长w 来更新 当前点x先往上走,到其他节点的最远距离up[x]

但是如何得到 fa往下走到底端,且不经过当前点x的最远距离 呢?

我们在第一种情况的 dfs 中已经维护了 fa 往下走到底端的最远距离 down[x],如果点 x 不在这个路径中,那么第一种情况中所维护的 down[x] 就是满足的;
但是如果点 x 在这个路径中,就需要找到 fa 往下走到底端的‘次远距离’。 (同样注意,这个 ‘次远距离’ 并不是真正的从 fa 到最底端的次远,而是再从 x 的其他兄弟节点中更新,所找到的最远距离。)
于是,就需要像上半部分求《树的直径》的递归一样,分别记录 最远距离d1[i] 和 次短距离d2[i]。同时用 ne[fa] 记录
fa 到底端的最远距离是用哪个子节点更新过来的,用于判断子节点 x 是否在最远距离的路径中

最后:

对于一个点x,向上走到其他点的最远距离up[x]向下走到其他点的最远距离d1[i]max,便是该点到其他所有点的最远距离
遍历所有点,取最远距离的最小值 便是 中心点到其他所有点最远距离的最小值。

for(int i=1;i<=n;i++)
	ans=min(ans,max(up[i],d1[i]));

 代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
int head[N],cnt;
int d1[N],d2[N];
int next[N];
int up[N];
struct ee
{
	int to,from,val;
}edge[N<<1];
void add(int u,int v,int w)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
int dfsd(int root,int fa)
{//从上往下递归,在回溯的时候由下往上更新
	for(int i=head[root];i;i=edge[i].from)
	{
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		int d=dfsd(v,root);
		if(d+w>d1[root])
		{
			d2[root]=d1[root];
			d1[root]=d+w;
			next[root]=v;
		}
		else if(d+w>d2[root])
		d2[root]=d+w;
	}
	return d1[root];
}
int dfsup(int root,int fa)
{//从上往下递归,从上往下更新
	for(int i=head[root];i;i=edge[i].from)
	{//遍历以root为根的所有子节点 
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		if(next[root]==v)
		up[v]=max(up[v],max(d2[root],up[root])+w);
		else
		up[v]=max(up[v],max(d1[root],up[root])+w);
		dfsup(v,root);
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n-1;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,w);
	}
	dfsd(1,0);
	dfsup(1,0);
	int ans=1e9;
	for(int i=1;i<=n;i++)
	ans=min(ans,max(up[i],d1[i]));
	cout<<ans<<endl;
}

写得太好了!!! 

 ------摘自经典问题《树的直径》与《树的中心》,详解。-CSDN博客

树形背包

这类题目有个大致的思路:

首先根据题述所给关系构建一个树,然后对每个点进行遍历,再结合背包算法得出状态转移方程即可

((感觉很水)xsbb)

树形dp&状态机

首先要明确状态的变化,确定状态机模型,然后用集合形式将状态表示出来,并定义其属性。接着进行状态计算得出状态转移方程。。。总之很难。。。

P2015 二叉苹果树

现在看来,这题其实也不是很难,属于肥肠水的模版啦

题目中可以看出有一个隐含条件,根据常识,一根树枝被保留下来时,那么从根节点到该节点的所有边都将会被保留下来,因此我们可以设dp[i][j]表示到根节点i节点之间保留了j根树枝时,留下的最多苹果

因此我们就可以得到这个方程了:

eq?f%5Bu%5D%5Bi%5D%3Dmax%28f%5Bu%5D%5Bi%5D%2Cf%5Bu%5D%5Bi-j-1%5D&plus;f%5Bv%5D%5Bj%5D&plus;edge%5Bi%5D.w%29

当然这题的精髓在于f[u][i-j-1]这个式子,i-j代表了除了保留这个树枝之外所保留的其他树枝(如上文),i-j-1代表u到v之间的这根树枝以及根节点到u节点之间的树枝

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1055;
int n,q;
int cnt;
int head[N];
int dp[N][N];
struct ee
{
	int to,from,val;
}edge[N<<1];
void add(int u,int v,int w)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs(int r,int fa)
{
	for(int i=head[r];i;i=edge[i].from)
	{
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		dfs(v,r);
		for(int j=q;j>=1;j--)
		for(int k=j-1;k>=0;k--)
		dp[r][j]=max(dp[r][j-k-1]+dp[v][k]+w,dp[r][j]);
	}
}
int main()
{
	cin>>n>>q;
	for(int i=1;i<=n-1;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,w);
	}
	dfs(1,0);
	cout<<dp[1][q]<<endl;
}

P1352 没有上司的舞会

很简单的一题了

f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值

f[x][1]表示以x为根的子树,且x参加了舞会的最大快乐值

eq?f%5Bx%5D%5B0%5D%3D%5Csum%20max%28f%5By%5D%5B0%5D%2Cf%5By%5D%5B1%5D%29 (y是x的儿子)

eq?f%5Bx%5D%5B1%5D%3D%5Csum%20f%5By%5D%5B0%5D&plus;h%5Bx%5D(y是x的儿子)

先找到唯一的树根root,然后开搜,显而易见最后答案一定是root参加舞会和不参加的f值的max

即ans=max(f[root][0],f[root][1])

#include<bits/stdc++.h>
using namespace std;
const int N=6e3+5;
int n;
int val[N];
int zi[N];
int dp[N][2];
int cnt; 
int head[N];
struct ee
{
	int to,from;
}edge[N];
void add(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs(int r)
{
	dp[r][0]=0;
	dp[r][1]=val[r];
	for(int i=head[r];i;i=edge[i].from)
	{
		int v=edge[i].to;
		dfs(v);
		dp[r][0]+=max(dp[v][1],dp[v][0]);
		dp[r][1]+=dp[v][0];
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>val[i];
	for(int i=1;i<=n-1;i++)
	{
		int k,l;
		cin>>l>>k;//k是l的上司 
		add(k,l);//以k为根,l为子 
		zi[l]=1;//标记是子的节点,下面找出最上根 
	}
	int root;
	for(int i=1;i<=n;i++)
	if(!zi[i])
	{
		root=i;
		break;
	}
	dfs(root);
	cout<<max(dp[root][0],dp[root][1])<<endl;
}

P8602 [蓝桥杯 2013 省 A] 大臣的旅费

所有的故事

源于这一题

对,没错,我就是写这题的时候被挡住了然后去学树形dp,然后回来一看发现不难了。。。

这题就是树的直径,找到最长路,然后直接代公式~~(题目推出来的):相当于从第0公里走到第max公里。。。

不过因为路是连着走的,所以需要设两个数组s1和s2来维护最大和次大,最后俩加起来就ok了

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
int cnt;
int head[N];
int ans;
int s1[N],s2[N];
struct ee
{
	int to,from,val;
}edge[N<<1];
void add(int u,int v,int w)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs(int r,int fa)
{
	for(int i=head[r];i;i=edge[i].from)
	{
		int v=edge[i].to;
		int w=edge[i].val;
		if(v==fa)
		continue;
		dfs(v,r);
		if(s1[v]+w>s1[r])
		{
			s2[r]=s1[r];
			s1[r]=s1[v]+w;
		}
		else if(s1[v]+w>s2[r])
		s2[r]=s1[v]+w;
	}
	ans=max(ans,s1[r]+s2[r]);
}
int main()
{
	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(1,0);
	cout<<ans*10+ans*(ans+1)/2<<endl;
}

P2016 战略游戏

这题是一个dp,我们发现,当一个节放了士兵,那么下一个放和不放就无所谓,可若该节点不放,那么下一个节点就必须要放,因此有了状态转移方程:

eq?%5Cleft%5C%7B%5Cbegin%7Bmatrix%7Ddp%5Br%5D%5B0%5D&plus;%3Ddp%5Bv%5D%5B1%5D%20%5C%5C%20dp%5Br%5D%5B1%5D&plus;%3Dmin%28dp%5Bv%5D%5B1%5D%2Cdp%5Bv%5D%5B0%5D%29%20%5Cend%7Bmatrix%7D%5Cright.

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1505;
int n;
int k;
int dp[N][2];
int cnt,head[N];
struct ee
{
	int to,from;
}edge[N<<1];
void add(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs(int r,int fa)
{
	dp[r][0]=0,dp[r][1]=1;
	for(int i=head[r];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v==fa)
		continue;
		dfs(v,r);
		dp[r][0]+=dp[v][1];
		dp[r][1]+=min(dp[v][1],dp[v][0]);
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int u,v;
		cin>>u>>k;
		for(int j=1;j<=k;j++)
		{
			cin>>v;
			add(u,v);
			add(v,u);
		}
	}
	dfs(0,-1);
//	for(int i=1;i<=n;i++)
//	{
//		for(int j=0;j<=1;j++)
//		cout<<dp[i][j]<<" ";
//		cout<<endl;
//	}
	cout<<min(dp[0][1],dp[0][0])<<endl;
}

P2018 消息传递

由题可知,想要传到所有人的时间最短,考虑到如果从任一点为根,向子节点传递时间较少的那个儿子先传过去,那么就会出现这边子节点传递完但是另外一边还没有传递完的情况,不难看出这是极其耗时的,效率很低。所以为了提高效率,让时间减少,我们应该从根节点向传递时间较多的子节点先传,然后耗时少的后传---这就是总体思路

具体一点:首先我们可以明确,每个节点都是可以作为根节点的,所以先将所有节点遍历一遍,对每个节点作为根节点从上往下搜,然后设置son数组,用kk计数,代表这一条路上有kk个子节点,son[kk]代表从最底端的节点往上传到第kk个节点消耗的时间,dp[v]代表根节点传到节点v所需要消耗的时间,于是有方程

eq?son%5Bkk%5D%3Ddp%5Bv%5D%3B 

表示从最底端向上回溯到第kk个节点,将从下面节点传到该节点的最大时间传给该节点 

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int n;
int dp[N];
int minn=1e9;
int head[N];
int ans[N];
int maxx;
int cnt;
struct ee
{
	int to,from;
}edge[N<<1];
void add(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs(int r,int fa)
{
	int kk=0;
	int son[N];
	for(int i=head[r];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v==fa)
		continue;
		dfs(v,r);
		kk++;
		son[kk]=dp[v];
		//遍历到第kk个儿子,将它的儿子的最大时间给它 
	}
	sort(son+1,son+kk+1,greater<int>());
	for(int i=1;i<=kk;i++)
	dp[r]=max(dp[r],son[i]+i);
	//因为由大到小排序,所以son[i]+i即为子节点将信息传到根节点的儿子的时间 
}
int main()
{
	cin>>n;
	for(int i=2;i<=n;i++)
	{
		int v;
		cin>>v;
		add(i,v);
		add(v,i);
	}
	for(int i=1;i<=n;i++)
	{
		memset(dp,0,sizeof(dp));
		dfs(i,0);
		ans[i]=dp[i];
		minn=min(minn,dp[i]);
	}
	cout<<minn+1<<endl;
	for(int i=1;i<=n;i++)
	if(ans[i]==minn)
	cout<<i<<" ";
	return 0;
}

P2014 [CTSC1997] 选课 

这题是一个很美妙的dp,它不仅有树还有背包,很巧妙的说。。

但也不是很难,很容易得到背包的状态转移方程:

eq?dp%5Bu%5D%5Bj%5D%3Dmax%28dp%5Bu%5D%5Bj%5D%2Cdp%5Bu%5D%5Bj-k%5D&plus;dp%5Bv%5D%5Bk%5D%29

表示:选择u科目,不选或选k个它的子节点v科目的附属科目所能得到的最大学分

代码:

#include<bits/stdc++.h> 
using namespace std;
const int N=305;
int n,m;
int cnt;
int dp[N][N];
int head[N];
struct e
{
	int to,from;
}edge[N<<1];
void add(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs(int u)
{
	for(int i=head[u];i;i=edge[i].from)
	{
		int v=edge[i].to;
		dfs(v);
		for(int j=m+1;j>=0;j--)
		for(int k=j-1;k>=0;k--)
		dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int k;
		cin>>k>>dp[i][1];
		add(k,i);//必学到附属学科 
	}
	dfs(0);
	cout<<dp[0][m+1]<<endl;
}

P2986 [USACO10MAR] Great Cow Gathering G

这题我觉得很水,不过很新颖,怎么说?

首先能想到的思路很简单,就是枚举每个节点作为根节点,将其当做要选择的点,计算其他点的奶牛到这个点的不方便度,最后找到最小值

不过这个思路有缺陷的,复杂度是O(n^2),过不去

看了题解后就明白了

我们先以1为根节点遍历一遍,将子节点的牛一点一点移到根节点,并且统计每一步的奶牛数,然后在第二个dfs里假设所有奶牛都到了1节点,从上往下遍历每个节点,按照原本路径(假设路径为u->v),u节点的奶牛移动到v节点,减去原本v节点的奶牛到u节点的不方便度,再加上u节点到v节点的不方便度(因为所有奶牛要到v节点),最后遍历每个点,将最小值加上原本到根节点1的不方便度即为解

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+5;
ll n;
ll sum;
ll cnt;
ll ans=1e9;
ll head[N];
ll f[N];
ll q[N];
ll c[N];
ll num[N];
ll dis[N];
struct e
{
	ll to,from,val;
}edge[N<<1];
void add(ll u,ll v,ll l)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].val=l;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
ll dfs1(ll u,ll fa)
{
	ll t=0;
	for(ll i=head[u];i;i=edge[i].from)
	{
		ll v=edge[i].to;
		ll w=edge[i].val;
		if(v==fa)
		continue;
		ll s=dfs1(v,u);//子树上的牛的数量 
		dis[u]+=dis[v]+s*w;
		t+=s;
	}
	q[u]=t+c[u];
	return q[u];
}
void dfs2(ll u,ll fa)
{
	for(ll i=head[u];i;i=edge[i].from)
	{
		ll v=edge[i].to;
		ll w=edge[i].val;
		if(v==fa)
		continue;
		f[v]=f[u]-q[v]*w+(sum-q[v])*w;
		dfs2(v,u);
	}
}
int main()
{
	cin>>n;
	for(ll i=1;i<=n;i++)
	{
		cin>>c[i];
		sum+=c[i];
	}
	for(ll i=1;i<=n-1;i++)
	{
		ll u,v,l;
		cin>>u>>v>>l;
		add(u,v,l);
		add(v,u,l);
	}
	dfs1(1,0);
	dfs2(1,0);
	for(ll i=1;i<=n;i++)
	ans=min(ans,f[i]);
	cout<<ans+dis[1]<<endl;
}

 资料:

动态规划——树形dp-CSDN博客

经典问题《树的直径》与《树的中心》,详解。-CSDN博客

AcWing 1073. 树的中心【树形DP+换根DP】 - AcWing

【动态规划】树形DP完全详解! - Koshkaaa - 博客园 (cnblogs.com)

树形 DP - OI Wiki (oi-wiki.org)


后续还是会写关于树形dp的题目的,这篇博客主要是用来总结一下这一周的成果,总之加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值