首页 > 其他分享 >CF1394D

CF1394D

时间:2022-10-29 08:00:07浏览次数:50  
标签:tmp cnt int ll ++ CF1394D sum

所谓单调递增或单调递减,其实只用看成一条有向的链,\(b\) 大的点指向 \(b\) 小的点即可。

所以对于边 \((u,v)\),假设 \(b_u\ne b_v\),那么这条边的方向确定。

一个点对答案的贡献是经过它的链数,设 \(x\) 为指向它的边的数量,\(y\) 为它指出去的边的数量,那么贡献就是 \(a_i\times \max(x,y)\)。

问题等价于你需要给两端高度相等的边确定方向最小化贡献和。

设 \(f_{u,0/1}\) 表示以 \(u\) 为根的子树,\(u\) 到父亲的边强制为连入 / 连出情况下的最小贡献和。

更具体的,问题变成给定 \(m\) 个二元组 \((f_{v,0},f_{v,1})\) 给每个二元组选择一个权值,然后设 \(x,y\) 分别为 \(0/1\) 的数量,贡献和加上 \(a_u\times\max(x,y)\)。

可以先假设不确定的所有元素选 \(0\),然后依次枚举 \(1\) 的数量,不难发现我们肯定优先选 \(f_{v,1}-f_{v,0}\) 尽可能小的元素。

然后即时更新一下贡献,特判一下根节点,这个题就做完了。

时间复杂度 \(\mathcal O(n\log n)\)。

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200005;
int n;
int a[N], b[N];
ll f[N][2];
int head[N], ver[N*2], nxt[N*2], cnt;
vector <ll> tmp[N];

void add(int u, int v) {
	ver[++cnt] = v, nxt[cnt] = head[u], head[u] = cnt;
}

void chkmin(ll &a, ll b) { if (a > b) a = b; }

void dfs(int u, int fa) {
	ll sum = 0, cnt1 = 0, cnt2 = 0;
	for (int i = head[u]; i; i = nxt[i]) {
		int v = ver[i];
		if (v == fa) continue;
		dfs(v, u);
		if (b[v] == b[u]) tmp[u].push_back(f[v][1] - f[v][0]), sum += f[v][0], ++cnt1;
		else if (b[v] > b[u]) sum += f[v][1], ++cnt2;
		else sum += f[v][0], ++cnt1;
	}
	sort(tmp[u].begin(), tmp[u].end());
	for (int i = 0; i <= tmp[u].size(); ++i) {
		chkmin(f[u][0], sum + max(cnt1, cnt2 + (u != 1)) * a[u]);
		chkmin(f[u][1], sum + max(cnt1 + (u != 1), cnt2) * a[u]);
		--cnt1, ++cnt2;
		if (i < tmp[u].size()) sum += tmp[u][i];
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
	for (int i = 1, u, v; i < n; ++i) scanf("%d%d", &u, &v), add(u, v), add(v, u);
	memset(f, 0x3f, sizeof f);
	dfs(1, 0);
	printf("%lld", min(f[1][0], f[1][1]));
	return 0;
}

标签:tmp,cnt,int,ll,++,CF1394D,sum
From: https://www.cnblogs.com/Kobe303/p/16837999.html

相关文章