• 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;
    }
    
  • 相关阅读:
    django-makdown编辑器之编辑和显示
    Jenkins以root用户运行的方法
    利用Django提供的ModelForm增删改数据的方法
    Django之URL反向解析
    腾讯云-tke-设置configmap
    线程同步
    测试人的福利来了!柠檬班题库免费对外开放啦。
    如何设计一个高并发系统?
    为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?
    记一次idea问题—performing vcs refresh...
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13753566.html
Copyright © 2020-2023  润新知