【JZOJ5776】小x游世界【换根法】【DP】【DFS】

本文介绍了一种解决特定树形结构问题的算法——树形动态规划(Tree DP)结合换根法,通过DFS预处理子树节点数量,再利用换根技巧O(1)求解每个节点作为根时的最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:

题目链接:https://jzoj.net/senior/#main/show/5776
题目图片:
http://wx4.sinaimg.cn/mw690/0060lm7Tly1fvjhzcpt7jj30j50f1aaw.jpg
http://wx1.sinaimg.cn/mw690/0060lm7Tly1fvjhzchmayj30j30e5t8x.jpg
http://wx4.sinaimg.cn/mw690/0060lm7Tly1fvjhzby695j30j506imx7.jpg


思路:

P.S.这道题题目描述太难写而我语文太菜没法简化所以各位大大就看图片吧orz。
这道题,我们先跑一边DFS,求出以1为根的时候的答案和每个点的子树节点数量(含自己)。
然后利用换根法(听说属于树形DP?),可以 O ( 1 ) O(1) O(1)求出每个点为根的时候的答案。最后取一个最优答案即可。


代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define N 701000
#define ll long long
using namespace std;

int n,c[N],head[N],son[N],u,tot=1;
ll sum[N],ans;
char ch;

struct edge
{
	int next,to,dis;
}e[N*2];

int read()  //不加快读会T
{
    u=0;
    while((ch=getchar())<=47||ch>=58);u=(u<<3)+(u<<1)+ch-48;
    while((ch=getchar())>=48&&ch<=57) u=(u<<3)+(u<<1)+ch-48;
    return u;
}

void add(int from,int to,int dis)
{
	e[++tot].to=to;
	e[tot].dis=dis;
	e[tot].next=head[from];
	head[from]=tot;
}

int dfs(int x,ll k,int fa)  //DFS
{
	int s=1;
	sum[1]+=k;  //sum[i]表示以第i个点为根的答案
	for (int i=head[x];~i;i=e[i].next)
	 if (e[i].to!=fa)
	  s+=dfs(e[i].to,k+(ll)e[i].dis,x);
	son[x]=s;  //son[x]表示以x为根的子树的节点数(含自己)
	return s;
}

void change(int x,int fa)  //换根
{
	for (int i=head[x];~i;i=e[i].next)
	 if (e[i].to!=fa)
	 {
	 	int y=e[i].to;
	 	sum[y]=sum[x]-(ll)son[y]*(ll)e[i].dis+(ll)(son[1]-son[y])*(ll)e[i^1].dis;
	 	//有些点要增加路径,而有些点要减少
	 	change(y,x);
	 }
}

int main()
{
	memset(head,-1,sizeof(head));
	n=read();
	for (int i=1;i<=n;i++)
	 c[i]=read();
	int x,y,z;
	for (int i=1;i<n;i++)
	{
		x=read();
		y=read();
		z=read();
		add(x,y,z-c[x]);
		add(y,x,z-c[y]);
	}
	dfs(1,0,-666);
	change(1,-666);
	ans=1e17;
	for (int i=1;i<=n;i++)
	 if (sum[i]<ans)  //取最小值
	 {
	 	ans=sum[i]; 
	 	x=i;
	 }
	cout<<x<<"\n"<<ans;
	return 0;
}
<think>嗯,用户问的是DP,也就是动态规划。我需要先回忆一下这个概念,确保自己理解正确。DP通常用于解决树形结构中的问题,特别是当需要在每个节点上计算某种依赖于子树或父节点的信息时。这种算的核心思想是通过两次遍历来高效地计算结果:一次后序遍历收集子树信息,然后一次前序遍历利用父节点的信息更新子节点的结果。 首先,我应该解释DP的基本概念,适用场景,然后给出一个具体的例子,比如计算树中每个节点到其他所有节点的距离之和。接下来,需要分步骤说明实现方,包括如何设计后序遍历收集数据,以及前序遍历如何利用这些数据进行更新。 然后,编写一个C++的示例代码。代码需要包括树的构建(邻接表表示),两次遍历的过程。后序遍历计算子树大小和初始距离和,前序遍历据父节点的信息调整子节点的距离和。注意处理节点的父节点,避免循环访问。 在解释部分,要详细说明每个步骤的作用,比如为什么需要子树大小,如何通过父节点的结果推导子节点的结果。同时,要指出时间复杂度是O(n),因为每个节点只被访问两次。 最后,提出相关问题,可能涉及DP的其他应用,与普通树形DP的区别,如何避免重复计算,时间复杂度分析等。确保问题覆盖用户可能想深入了解的方面。 需要检查是否有错误,例如代码中的邻接表是否正确处理无向树,是否在遍历时排除了父节点,避免循环。确保示例的正确性,比如计算距离和的公式是否正确,时的调整是否正确。</think><< DP动态规划)是一种用于树形结构的高效算,主要用于解决需要在每个节点上计算某种全局性质的问题。其核心思想是通过两次遍历: 1. **后序遍历**:从任意节点(通常选0/1)出发,计算子树信息 2. **前序遍历**:利用父节点的计算结果推导子节点作为新时的结果 典型应用场景:计算树中每个节点到其他所有节点的距离之和 **实现步骤**: 1. 建立邻接表存储树结构 2. 第一次DFS计算子树大小和初始节点的答案 3. 第二次DFS通过父节点答案推导子节点答案 **示例代码**(计算所有节点到其他节点的距离和): ```cpp #include <vector> using namespace std; vector<vector<int>> adj; // 邻接表 vector<int> res; // 存储每个节点的最终结果 vector<int> count; // 子树节点数(包含自身) int n; // 节点总数 void post_order(int u, int parent) { count[u] = 1; for (int v : adj[u]) { if (v == parent) continue; post_order(v, u); count[u] += count[v]; res[u] += res[v] + count[v]; // 累加子树距离 } } void pre_order(int u, int parent) { for (int v : adj[u]) { if (v == parent) continue; // 当从u到v时:其他节点的数量是n - count[v] res[v] = res[u] - count[v] + (n - count[v]); pre_order(v, u); } } vector<int> sumDistancesInTree(int _n, vector<vector<int>>& edges) { n = _n; adj.resize(n); res.resize(n); count.resize(n); for (auto& e : edges) { adj[e[0]].push_back(e[1]); adj[e[1]].push_back(e[0]); } post_order(0, -1); // 以0为初始化 pre_order(0, -1); // 计算所有节点 return res; } ``` **关键解释**: 1. `post_order`计算初始节点(0)的结果,通过累加子树距离 2. `pre_order`利用公式推导:当从u切到子节点v时,距离变化为: - 减少v子树的count[v]个节点各1距离(共减少count[v]) - 增加非v子树的(n - count[v])个节点各1距离 3. 时间复杂度O(n),每个节点访问两次
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值