【树形dp】W

题面

在这里插入图片描述

题意

给出一棵树,每条边有初始状态。对一条路径上的边的状态进行翻转,求每条边满足结果且操作数最少,最小的操作边数之和。

思路

设f[i][0/1]表示i与fa[i]的边是否需要翻转时的答案。
枚举每个儿子,判断儿子与i的边是否处于同一个操作次数(等方案)进行转移。
在这里插入图片描述

代码

#include <vector>
#include <cstdio>
#include <stdlib.h>
#include <algorithm>

const std::pair<int, int> inf = std::make_pair(1e9, 1e9);
std::vector<std::pair<int, int> > g[200001];
std::pair<int, int> f[100001][2];
int n;

std::pair<int, int> operator+ (std::pair<int, int> a, std::pair<int, int> b) {
	return std::make_pair(a.first + b.first, a.second + b.second);
}

void dfs(int fa, int p, int type) {
	std::pair<int, int> tmp0(0, 0), tmp1(inf);
	for (int i = 0; i < g[p].size(); i++) {
		int v = g[p][i].first;
		if (v == fa) continue;
		dfs(p, v, g[p][i].second);
		std::pair<int, int> nxt0, nxt1;
		nxt0 = std::min(tmp0 + f[v][0], tmp1 + f[v][1]);
		nxt1 = std::min(tmp0 + f[v][1], tmp1 + f[v][0]);
		tmp0 = nxt0;
		tmp1 = nxt1;
	}
	if (!type || type == 2)
		f[p][0] = std::min(tmp0, std::make_pair(tmp1.first + 1, tmp1.second));
	else f[p][0] = inf;
	if (type == 1 || type == 2)
		f[p][1] = std::min(std::make_pair(tmp0.first + 1, tmp0.second + 1), std::make_pair(tmp1.first, tmp1.second + 1));
	else f[p][1] = inf;
}

int main() {
	int size = 256 << 20;
    char *p = (char*)malloc(size) + size;
    __asm__("movl %0, %%esp\n" :: "r"(p));
	scanf("%d", &n);
	for (int i = 1, a, b, c, d; i < n; i++) {
		scanf("%d %d %d %d", &a, &b, &c, &d);
		if (d != 2) d = c ^ d;
		g[a].push_back(std::make_pair(b, d));
		g[b].push_back(std::make_pair(a, d));
	}
	dfs(0, 1, 0);
	printf("%d %d", f[1][0].first / 2, f[1][0].second);
}
### 树形 DP 的实现与应用 #### 什么是树形动态规划? 树形动态规划(Tree-shaped Dynamic Programming, Tree DP)是一种基于树结构的动态规划方法。它的核心思想是利用树的层次性和递归性质,将复杂问题分解成更简单的子问题,并通过自底向上的方式逐步求解最终目标[^1]。 --- #### 树形 DP 的基本原理 树形 DP 基于以下几点特性: - **无环性**:由于树本身是一个无环图,因此可以方便地按照节点之间的父子关系进行状态转移。 - **分治策略**:通过对每个节点及其子树分别计算最优解,再将其组合起来得到全局最优解。 - **记忆化存储**:通常会用数组或其他数据结构保存中间结果,减少重复计算的时间开销。 对于任意一个节点 \( u \),其状态可以通过对其所有子节点的状态进行聚合来更新。这种依赖关系使得树形 DP 成为了许多经典问题的有效解决方案。 --- #### 树形 DP 的典型应用场景 以下是几个常见的树形 DP 应用场景: ##### 1. 树的最大独立集 (Maximum Independent Set on Trees) 该问题是寻找一颗树中的最大点集合,其中任何两点之间都没有边相连。 设 \( f[u][0/1] \) 表示以节点 \( u \) 为根的子树中,\( u \) 是否被选入独立集中时的最大大小,则有如下状态转移方程: \[ f[u][0] = \sum_{v\in child(u)} max(f[v][0], f[v][1]) \] \[ f[u][1] = w_u + \sum_{v\in child(u)} f[v][0] \] 这里 \( v \) 是 \( u \) 的孩子节点,而 \( w_u \) 则表示节点 \( u \) 自身的价值[^3]。 --- ##### 2. 树的直径 (Diameter of a Tree) 树的直径是指树中最远两个节点间的距离。一种常见做法是从某个起点出发做一次 DFS 找到最远点 A ,然后再从 A 出发找到另一个最远点 B 。AB 距离即为所求直径长度[^2]。 另一种单次遍历的方式也可以完成此任务,但需额外维护路径信息以便后续验证正确性。 ```cpp // C++ 实现树的直径算法 #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5+7; vector<int> adj[MAXN]; int dist[MAXN]; void dfs(int node, int parent){ for(auto &child : adj[node]){ if(child != parent){ dist[child] = dist[node]+1; // 记录当前深度 dfs(child,node); } } } int main(){ int n; cin>>n; for(int i=1;i<n;i++){ int u,v; cin>>u>>v; adj[u].push_back(v); adj[v].push_back(u); } memset(dist,-1,sizeof dist);dist[1]=0; dfs(1,-1); int farthest_node_1 = max_element(dist+1,dist+n+1)-dist; memset(dist,-1,sizeof dist);dist[farthest_node_1]=0; dfs(farthest_node_1,-1); cout << *max_element(dist+1,dist+n+1)<< "\n"; // 输出直径 } ``` 上述代码实现了两次DFS的过程,第一次定位端点A,第二次由A扩展至B并记录两者间最长链路作为答案。 --- ##### 3. 最优伐木选址问题 这是一个较为复杂的实际案例,在给定的一棵带权重树上选取若干个位置设立伐木厂,使总运输成本最低。具体来说就是让每片区域内的木材都运往最近的一个工厂处加工处理。假设已知各顶点及连结它们的边都有相应数值属性关联的话,那么就可以借助动态规划技巧加以优化解决这个问题了。 令 `f[i][j]` 表达当第i号节点设置j座锯木场时候所能达到的最佳效益值;于是乎针对每一个可能的情况逐一尝试枚举分配方案即可得出结论。 --- #### 总结 综上所述,树形 DP 不仅理论基础扎实而且实践价值极高,无论是学术研究还是工业开发领域都有着广泛的应用前景。掌握好这类技术能够帮助开发者更加高效精准地应对各种挑战性的编程难题^。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值