【2022 省选训练赛 Contest 15 C】老园丁与小司机(左偏树)(DP)

老园丁与小司机

题目链接:2022 省选训练赛 Contest 15 C

题目大意

给你一棵树,然后有一些路径(满足路径的两端的点的 LCA 是其中之一),每个路径有选的费用。
然后要你花费最小的费用使得每个边都在选的至少一条路径中,或输出无解。

思路

考虑到路径的特别,我们可以有这么一种 DP。
f i , j f_{i,j} fi,j 为解决了 i i i 的子树,并且也解决了一条往上到 j j j 的链的最小费用。
那每次合并两个子树就找到它们里面各自最小的,给对面加上,然后合并两个子树的值。
然后跟所有的儿子合并完之后,我们要把不合法的踢出(就是 j j j 的深度小于等于 i i i,那到处理上面的点的时候它就不合法了)
(当然是 1 1 1 就不用踢了)

然后考虑能不能优化这个过程。
看到合并,求最小的,踢出不合法(而且你会发现每次就是不断踢深度最小就可以了)
那我们可以用可并堆【按深度排,然后维护子树最小值,子树加值用懒标记】,用左偏树来实现即可。

代码

#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

const int N = 300000 + 100;
int n, m, rt[N], tot, deg[N];
vector <int> G[N];
vector <pair<int, int> > L[N];

struct ZP_Tree {
	ll val[N], lzy[N], minn[N];
	int ls[N], rs[N], h[N], deg[N];
	
	void up(int now) {
		minn[now] = val[now];
		if (ls[now]) minn[now] = min(minn[now], minn[ls[now]]);
		if (rs[now]) minn[now] = min(minn[now], minn[rs[now]]);
	}
	
	void downa(int x, ll k) {
		val[x] += k; minn[x] += k; lzy[x] += k;
	}
	
	void down(int now) {
		if (lzy[now]) {
			if (ls[now]) downa(ls[now], lzy[now]);
			if (rs[now]) downa(rs[now], lzy[now]);
			lzy[now] = 0;
		}
	}
	
	int merge(int x, int y) {
		if (!x || !y) return x + y;
		if (deg[x] < deg[y]) swap(x, y);
		down(x);
		rs[x] = merge(rs[x], y);
		if (h[rs[x]] >= h[ls[x]]) swap(rs[x], ls[x]);
		h[x] = h[rs[x]] + 1;
		up(x); return x;
	}
	
	int delete_(int x) {
		down(x); return merge(ls[x], rs[x]);
	}
}T;

void dfs(int now, int father) {
	deg[now] = deg[father] + 1;
	for (int i = 0; i < G[now].size(); i++) {
		int x = G[now][i]; if (x == father) continue;
		dfs(x, now);
		if (!rt[x]) {
			printf("-1"); exit(0);
		}
		if (!rt[now]) {
			rt[now] = rt[x]; continue;
		}
		ll vx = T.minn[rt[x]], vy = T.minn[rt[now]];
		T.downa(rt[now], vx);
		T.downa(rt[x], vy);
		rt[now] = T.merge(rt[now], rt[x]);
	}
	ll va = T.minn[rt[now]];
	for (int i = 0; i < L[now].size(); i++) {
		int v = L[now][i].first, w = L[now][i].second;
		++tot; T.deg[tot] = deg[v]; T.val[tot] = T.minn[tot] = va + w;
		rt[now] = T.merge(rt[now], tot);
	}
	if (now != 1) {
		while (rt[now] && T.deg[rt[now]] >= deg[now]) {
			rt[now] = T.delete_(rt[now]);
		}
	}
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i < n; i++) {
		int x, y; scanf("%d %d", &x, &y);
		G[y].push_back(x); G[x].push_back(y);
	}
	for (int i = 1; i <= m; i++) {
		int u, v, c; scanf("%d %d %d", &u, &v, &c);
		L[u].push_back(make_pair(v, c));
	}
	
	dfs(1, 0);
	printf("%lld", T.minn[rt[1]]);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值