Plutotree

题目蓝链

Solution

虽然这题加了很多边,但本质上还是一棵树,我们只需要维护一下链上的和与最大值,然后直接树形DP

但不过这道题的DP比较独特,因为一个节点的DP值可以从它的父亲更新过来。那么这个DP是不是就是有后向性了呢?

其实我们只需要DP两遍就可以了,第一遍DP处理出一个点到它的子树内的最优值。第二遍的时候,对于每一个点\(i\)记录一下一个权值\(dp_i - pre_i\)(\(pre_i\)为当前点到根的前缀和),在更新\(i\)号点的时候,我们在它的所有祖先中找一个这个权值最小的点\(j\),然后用\(dp_j - pre_j + pre_i\)来更新当前点就可以了

Code

#include <bits/stdc++.h>

using namespace std;

#define squ(x) ((LL)(x) * (x))
#define debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef pair<int, int> pii;

inline int read() {
	int sum = 0, fg = 1; char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') fg = -1;
	for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30);
	return fg * sum;
}

const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;

int n, q;

namespace Tree {

	vector<int> g[maxn];
	int d[maxn], fa[maxn][17], Max[maxn][17];
	int w[maxn], pre[maxn];
	pii dp[maxn];

	void input() {
		for (int i = 2; i <= n; i++) g[read()].push_back(i);
		for (int i = 1; i <= n; i++) w[i] = read();
	}

	void dfs1(int now, int f) {
		dp[now] = (pii){inf, inf}, pre[now] = pre[f] + w[now];
		d[now] = d[f] + 1, fa[now][0] = f, Max[now][0] = max(w[now], w[f]);
		for (int i = 1; i <= 16; i++) {
			fa[now][i] = fa[fa[now][i - 1]][i - 1];
			Max[now][i] = max(Max[now][i - 1], Max[fa[now][i - 1]][i - 1]);
		}
		for (int son : g[now]) {
			dfs1(son, now);
			pii tmp = (pii){dp[son].first + w[now], min(dp[son].second, -w[now])};
			dp[now] = min(dp[now], tmp);
		}
		if (dp[now] == (pii){inf, inf}) dp[now] = (pii){w[now], -w[now]};
	}

	void dfs2(int now, pii pp) {
		pii tmp = (pii){pp.first + pre[now], min(pp.second, -w[now])};
		dp[now] = min(dp[now], tmp);
		tmp = (pii){dp[now].first - pre[now], dp[now].second};
		pp = min(pp, tmp);
		for (int son : g[now]) dfs2(son, pp);
	}

	int lca(int x, int y) {
		if (d[x] < d[y]) swap(x, y);
		for (int i = 16; ~i; i--)
			if (d[fa[x][i]] >= d[y]) x = fa[x][i];
		if (x == y) return x;
		for (int i = 16; ~i; i--)
			if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
		return fa[x][0];
	}

	int sum(int x, int y) {
		int f = lca(x, y), ff = fa[f][0];
		return pre[x] + pre[y] - pre[f] - pre[ff];
	}

	int get_max(int x, int y) {
		int MAX = max(w[x], w[y]), ff = lca(x, y);
		for (int i = 16; ~i; i--)
			if (d[fa[x][i]] >= d[ff]) MAX = max(MAX, Max[x][i]), x = fa[x][i];
		for (int i = 16; ~i; i--)
			if (d[fa[y][i]] >= d[ff]) MAX = max(MAX, Max[y][i]), y = fa[y][i];
		return MAX;
	}

	void query(int x, int y) {
		pii ans = (pii){sum(x, y), -get_max(x, y)};
		pii t1 = (pii){dp[x].first + pre[y], min(-get_max(y, 1), dp[x].second)};
		pii t2 = (pii){dp[y].first + pre[x], min(-get_max(x, 1), dp[y].second)};
		pii t3 = (pii){dp[x].first + dp[y].first + w[1], min(-w[1], min(dp[x].second, dp[y].second))};
		ans = min(ans, t1), ans = min(ans, t2), ans = min(ans, t3);
		printf("%d %d\n", ans.first, -ans.second);
	}

}

int main() {
	freopen("plutotree.in", "r", stdin);
	freopen("plutotree.out", "w", stdout);

	n = read(), q = read();
	Tree::input();
	Tree::dfs1(1, 0), Tree::dfs2(1, (pii){inf, inf});

	while (q--) {
		int x = read(), y = read();
		Tree::query(x, y);
	}

	return 0;
}

Summary

我是真的菜,一开始一直以为这题不能DP....

这是我第一次接触这种多次更新答案的树形DP题,又学到了一个新的技巧

posted @ 2018-09-23 15:35  xunzhen  阅读(245)  评论(0编辑  收藏  举报