2/5 日哦咦咦啊咦哦咦咦咦啊咦书上背包进阶练习+换根dp初步大学习总结

哦咦咦啊咦哦咦咦咦啊咦哦咦咦啊咦哦咦咦咦啊咦哦咦咦啊咦哦咦咦咦啊咦

\(O(n^2)\) 树上背包

伪(人)代码:

for v in graph[u]:
	 for i from 1 to size[u]:
		 for j from 1 to size[v]:
			 dp转移
	size[u]+=size[v]

复杂度证明

这实际上是在枚举点对,\(i\) 代表从 \(u\) 之前已经合并的子树中选出的节点数量。
\(j\) 代表从当前子节点 \(v\) 的子树中选出的节点数量。

对于整棵树中的任意两个节点 \(x\),\(y\),它们一定有一个最近公共祖先(LCA),设为 \(L\)。

在遍历的过程中,只有当 DFS 回溯到 \(L\) 时,\(x\) 和 \(y\) 会进入到同一个状态转移方程里计算。

在 \(L\) 处,必然有一个时刻,正在把包含 \(x\) 的子分支合并到包含 \(y\) 的主分支(或者反过来)。

这时,内层循环枚举到了 \(x\) 所在的那部分大小,外层循环枚举到了 \(y\) 所在的那部分大小,于是 \(x\) 和 \(y\) 产生了一次贡献。

所以,对于任意两个节点,只会在它的 LCA 处被计算一次。

复杂度:\(O(\frac{n\cdot(n-1)}{2})\approx O(n^2)\)

\(O(nm)\) 树上背包

伪代码:

for v in graph[u]:
	for i from 1 to min(size[u],m):
		for j from 1 to min(size[v],m):
			dp转移
	size[u]+=size[v]

复杂度证明

写不下了,记得看课件!

注意事项

注意i、j循环的顺序!
可能写反了WA0分,正了就是AC。

染色那道题j倒序就AC原因未知,正在查明中...

换根dp初步

例题:HDU 2196 Computer

暴力:求出对于任意节点 \(u\),都去搜索一次,找出最远的点。显然炸。

原因: 做了很多重复的事情。

正解:贪心

换根dp

注意上面的图,唯一的变化就是 \(u\)、\(v\) 的父子关系转化了。\(A\) \(B\) 显然可以重复利用。

我们设 \(d[u]\)表示从 \(u\) 出发向其子树方向能达到的最大距离。

当换根时,唯一的变化就是多了个儿子 \(u\),需要把 \(u\) 的贡献加入 \(dis[v]\) 中。

如果选择 1 作为根节点,那么在一次DFS之后,关于 1 到其他节点的最大距离就出来了。

现在考虑如何求解其他点的答案。假设当前将根从父节点 \(u\) 向子节点 \(v\) 转移。

有可能最深的点还在子树 \(v\) 内,那么这种情况下的答案是 \(dis[v]\)。

也有可能最深的点不在子树 \(v\) 内,就在 \(u\) 和 \(u\) 的其他儿子内。那么这时候,最长的路会从 \(u-X\) 变为 \(u-v-X\),即 \(dis[u]+w(u,v)\)。

注意!有可能 dis[u] 对应的点就是在 v 子树内!所以答案不是 \(\max(dis[v],dis[u]+w(u,v))\)

但是肯定,从 v-u-B 里面肯定还有可能成为答案。例如下图。

换根2

所以可以选次长路径。

思路

  • 任选一个根结点进行DFS,记录从每个点出发,能够到达的最远距离 dis[u,0] 和次远的距 dis[u,1]。
  • 接下来进行换根:先保留之前搜到的 dis[v,0] 和 dis[v,1] 不动。如果 dis[u,0] == dis[v,0] + val(u,v) 成立, dis[v,0] 通过 dis[u,1] + val(u,v) 进行更新。否则,dis[v,1] 通过 dis[u,0] + val(u,v) 进行更新

AC code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int d1[10005],d2[10005],ans[10005];
vector<pair<int,int>> g[10005];
void dfs1(int u,int Fa){
	for(auto t:g[u]){
		int v=t.first,w=t.second;
		if(v==Fa) continue;
		dfs1(v,u);
		int val=d1[v]+w;
		if(val>d1[u]) swap(d1[u],val);
		if(val>d2[u]) swap(d2[u],val);
	}
}
void dfs2(int u,int Fa){
	for(auto t:g[u]){
		int v=t.first;
		long long w=t.first;
		if(v==Fa) continue;
		long long val=0;
		if(d1[v]+w==d1[u]) val=d2[u]+w;
		else val=d1[u]+w;
		if(val>d1[u]) swap(d1[v],val);
		if(val>d2[u]) swap(d2[v],val);
		ans[v]=d1[v];
		dfs2(v,u);
	}
}
void solve(int n){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	for(int i=2,v,w;i<=n;i++){
		cin>>v>>w;
		g[i].push_back(make_pair(v,w));
		g[v].push_back(make_pair(i,w));
	}
	dfs1(1,0);
	dfs2(1,0);
	for(int i=1;i<=n;i++){
		cout<<d1[i]<<"\n";
		g[i].clear();
		d2[i]=d1[i]=0;
	}
}
signed main(){
	int n;
	while(cin>>n){
		solve(n);
	}
}

posted @ 2026-02-05 19:26  vivid/stasis  阅读(42)  评论(0)    收藏  举报