CF1394D Boboniu and Jianghu

CF1394D Boboniu and Jianghu

题目大意

题目链接

波波牛每天都在山上练习功夫。

波波牛设计了一张 \(n\) 座山的地图。他用 \(n-1\) 条边将山相连,保证整张图联通,也就是说,这张图形成了一个的结构。

对于第 \(i\) 座山,波波牛评估了在这座山上练功夫的疲惫值 \(t_i\)。同时,他还计算出了每座山的高度 \(h_i\)

我们定义一条路径,是一个山的序列 \(M\),满足 \(\forall 1\leq i < |M|\)\(M_i\)\(M_{i+1}\) 之间都有边相连。波波牛认为一条路径是一个挑战,当且仅当 \(\forall 1\leq i < |M|:h_{M_i}\leq h_{M_{i + 1}}\)

波波牛会将所有 \(n-1\) 条边划分成若干个挑战,使得每条都恰好出现在一个挑战里。

我们定义一个挑战的疲惫值为该挑战里所有节点的疲惫值之和,即:\(\sum_{i=1}^{|M|}t_{M_i}\)

请你划分这些挑战,使得所有挑战的疲惫值之和最小。你只需要输出这个最小的疲惫值。

数据范围:\(1\leq n\leq 2\cdot 10^5\)

shoufu

本题题解

考虑 \(h\) 单调这个要求。我们可以理解为给边定向,使得同一条路径上的边方向相同(也就是从某个端点可以依次按方向经过所有边到达另一个端点)。不妨约定,对于树上的边 \((x,y)\),如果方向是 \(x\rightarrow y\),则 \(h_x\leq h_y\)。也就是说,当 \(h_x\neq h_y\) 时,边的方向是已经确定的。我们要做的是给所有 \(h_x=h_y\) 的边定一个方向。

定好方向后,如何计算答案?首先,一种一定合法的方案是,每条边单独作为一条路径。此时的代价和是 \(\sum_{i = 1} ^ {n} \text{deg}(i)\times t_i\),其中 \(\text{deg}(i)\) 表示树上节点 \(i\) 的度数。但这显然不一定是最优方案。我们来做一些调整使它变得更优。选取一个节点 \(u\),一条以 \(u\) 为终点的路径和一条以 \(u\) 为起点的路径拼接起来,则总代价会减少 \(t_u\)。我们希望通过拼接一些路径,使得总代价的“减少值”最大

综上所述,我们的工作有两个:

  1. 给边定向。
  2. 将定向好的边拼接。

这两个工作可以通过一次树形 DP 同时完成。

先任意钦定一个根节点。设 \(dp[u][0]\) 表示节点 \(u\) 和父亲 \(fa\) 之间的边方向是 \(u\rightarrow fa\),只考虑 \(u\) 子树内的节点,它们最多能使总代价减少多少;\(dp[u][1]\) 表示方向是 \(fa\rightarrow u\) 时,最多减少多少。特别地,对于根节点而言,它没有父亲,所以 \(dp[\text{root}][0] = dp[\text{root}][1]\)。值得注意的是,DP 时计算的并不是答案,\(\sum_{i = 1} ^ {n} \text{deg}(i)\times t_i-dp[\text{root}][0]\) 才是答案。所以我们要使 DP 值尽可能

转移,考虑 \(u\) 的儿子 \(v\)。可以分成三类:

  1. \(h_v<h_u\)
  2. \(h_v>h_u\)
  3. \(h_v = h_u\)

设三类儿子的数量分别为 \(c_1,c_2,c_3\)。为了方便讨论,不妨假设 \(c_1\leq c_2\)\(c_1>c_2\) 时做法类似)。

对于前两类,方向是确定的,它们的 DP 值(第 1 类 \(dp[v][0]\),第 2 类 \(dp[v][1]\))可以直接加到 \(dp[u]\) 里。并且可以先将它们尽可能多地两两配对,每成功匹配一对就使 \(dp[u]\) 增大 \(t_u\)。于是我们会匹配掉 \(c_1\) 对,并且剩下 \(c_2-c_1\) 个落单的第 2 类儿子。

接下来,问题的关键是第 3 类儿子如何定向。考虑有 \(i\) 个第 3 类儿子方向定为 \(v\rightarrow u\),其他 \(c_3-i\) 个定为 \(u\rightarrow v\)。那么具体选哪 \(i\) 个呢?发现两种方向对 \(dp[u]\) 的贡献分别为 \(dp[v][0]\)\(dp[v][1]\),所以我们可以将第三类儿子按 \(dp[v][0]-dp[v][1]\) 的值从大到小排序。然后取前 \(i\) 大的即可。

定向后,这些儿子要进行匹配。匹配有两种:和落单的第 2 类儿子匹配,或者两种方向的第 3 类儿子内部匹配。枚举了 \(i\),很容易 \(O(1)\) 计算出匹配的数量。

最后总结一下,\(dp[u]\) 的值来自两个部分,分别是 \(dp[v]\),以及 \(t_u\times\text{新增的匹配数}\)。对前两类儿子来说,\(dp[v]\) 的贡献是确定的,对第 3 类儿子,我们枚举有 \(i\)\(dp[v][0]\),然后贪心取 \(dp[v][0] - dp[v][1]\)\(i\) 大的即可。新增的匹配数,又可以分为四个部分:(1) 前两类儿子,尽可能多匹配;(2) 前两类儿子匹配完多出来的部分,和第 3 类儿子匹配;(3) 第 3 类儿子内部匹配;(4) 如果最后多 \(1\) 条边,可能可以和 \((u,fa)\) 匹配(视 \((u,fa)\) 的方向而定)。

时间复杂度 \(O(n\log n)\),其中 \(\log n\) 来自排序。

参考代码

实际提交时,建议加上读入优化,详见本博客公告。

// problem: CF1394D
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 2e5;

int n, tired[MAXN + 5], hight[MAXN + 5];
ll dp[MAXN + 5][2];
vector<int> G[MAXN + 5];
vector<pair<ll, int> > eq[MAXN + 5];

void dfs(int u, int fa) {
	ll sum_le = 0, sum_gr = 0;
	int cnt_le = 0, cnt_gr = 0;
	for(int i = 0; i < SZ(G[u]); ++i) {
		int v = G[u][i];
		if(v == fa) continue;
		dfs(v, u);
		if(hight[v] < hight[u]) {
			cnt_le++;
			sum_le += dp[v][0];
		}
		else if(hight[v] > hight[u]) {
			cnt_gr++;
			sum_gr += dp[v][1];
		}
		else {
			eq[u].pb(mk(dp[v][0] - dp[v][1], v));
		}
	}
	
	if(cnt_le <= cnt_gr) {
		ll sum = sum_le + sum_gr + (ll)cnt_le * tired[u];
		int rest = cnt_gr - cnt_le;
		sort(eq[u].begin(), eq[u].end());
		reverse(eq[u].begin(), eq[u].end()); // 从大到小
		ll eqcur = 0;
		for(int i = 0; i < SZ(eq[u]); ++i) eqcur += dp[eq[u][i].se][1];
		ckmax(dp[u][0], sum + eqcur);
		ckmax(dp[u][1], sum + eqcur + (u != 1 && rest + SZ(eq[u]) > 0) * tired[u]);
		for(int i = 1; i <= SZ(eq[u]); ++i) {
			// eq 里选几个 0
			eqcur += eq[u][i - 1].fi;
			int mat = 0, have_0 = 0, have_1 = 0;
			if(i <= rest) {
				mat = i;
				have_1 = (i < rest || i < SZ(eq[u]));
			} else {
				mat = rest + min(i - rest, SZ(eq[u]) - i);
				have_0 = (i - rest > SZ(eq[u]) - i);
				have_1 = (i - rest < SZ(eq[u]) - i); 
			}
			if(u == 1) have_0 = have_1 = 0;
			ckmax(dp[u][0], sum + eqcur + (ll)(mat + have_0) * tired[u]);
			ckmax(dp[u][1], sum + eqcur + (ll)(mat + have_1) * tired[u]);
		}
	} else {
		ll sum = sum_le + sum_gr + (ll)cnt_gr * tired[u];
		int rest = cnt_le - cnt_gr;
		sort(eq[u].begin(), eq[u].end());
		ll eqcur = 0;
		for(int i = 0; i < SZ(eq[u]); ++i) eqcur += dp[eq[u][i].se][0];
		ckmax(dp[u][0], sum + eqcur + (u != 1 && rest + SZ(eq[u]) > 0) * tired[u]);
		ckmax(dp[u][1], sum + eqcur);
		for(int i = 1; i <= SZ(eq[u]); ++i) {
			// eq 里选几个 1
			eqcur -= eq[u][i - 1].fi;
			int mat = 0, have_0 = 0, have_1 = 0;
			if(i <= rest) {
				mat = i;
				have_0 = (i < rest || i < SZ(eq[u]));
			} else {
				mat = rest + min(i - rest, SZ(eq[u]) - i);
				have_0 = (i - rest < SZ(eq[u]) - i);
				have_1 = (i - rest > SZ(eq[u]) - i);
			}
			if(u == 1) have_0 = have_1 = 0;
			ckmax(dp[u][0], sum + eqcur + (ll)(mat + have_0) * tired[u]);
			ckmax(dp[u][1], sum + eqcur + (ll)(mat + have_1) * tired[u]);
		}
	}
}
int main() {
	cin >> n;
	for(int i = 1; i <= n; ++i)
		cin >> tired[i];
	for(int i = 1; i <= n; ++i)
		cin >> hight[i];
	for(int i = 1; i < n; ++i) {
		int u, v; cin >> u >> v;
		G[u].pb(v); G[v].pb(u);
	}
	dfs(1, 0);
	ll ans = 0;
	for(int i = 1; i <= n; ++i)
		ans += (ll)tired[i] * SZ(G[i]);
	// cerr << ans << endl;
	assert(dp[1][0] == dp[1][1]);
	ans -= dp[1][0];
	cout << ans << endl;
	// int i, j; while(1) { cin >> i >> j; cout << dp[i][j] << endl; }
	return 0;
}
posted @ 2020-09-30 11:11  duyiblue  阅读(321)  评论(1编辑  收藏  举报