CF1394D Boboniu and Jianghu

题目来源:CodeforcesCodeforces Round #664 (Div. 1),CF1394,CF1394D,Boboniu and Jianghu。出题人:高爸,高麟翔,CF id:ZZZZZZZZZZZZZZZZZZ

题目大意

题目链接

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

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

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

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

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

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

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

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

shoufu

本题题解

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

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

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

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

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

先任意钦定一个根节点。设 (dp[u][0]) 表示节点 (u) 和父亲 (fa) 之间的边方向是 (u ightarrow fa),只考虑 (u) 子树内的节点,它们最多能使总代价减少多少;(dp[u][1]) 表示方向是 (fa ightarrow 1) 时,最多减少多少。特别地,对于根节点而言,它没有父亲,所以 (dp[ ext{root}][0] = dp[ ext{root}][1])。值得注意的是,DP 时计算的并不是答案,(sum_{i = 1} ^ {n} ext{deg}(i) imes t_i-dp[ ext{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_1leq 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 ightarrow u),其他 (c_3-i) 个定为 (u ightarrow v)。那么具体选哪 (i) 个呢?发现两种方向对 (dp[u]) 的贡献分别为 (dp[v][0])(dp[v][1]),所以我们可以将第三类儿子按 (dp[v][0]-dp[v][1]) 的值从大到小排序。然后取前 (i) 大的。你可以把这个策略理解为:先假设全取 (dp[v][1]),然后贪心挑 (i) 个换成 (dp[v][0])

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

最后总结一下,(dp[u]) 的值来自两个部分,分别是 (dp[v]),以及 (t_u imes ext{新增的匹配数})。对前两类儿子来说,(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(nlog 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;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/13753566.html