bzoj3611 大工程

大工程

题目背景:

bzoj3611

分析:虚树 + 树型DP

 

还真是个大工程(笑

讲题,一看到条件给出了k的和不超过2 * n那么显然是虚树了,对于每一个询问的关键点建立虚树,然后考虑如何DP,我们比较显然的每一条边对和的贡献就是两边的关键点个数之积,我们定义size[i]表示i的子树中的关键点个数,那么对于dis(i, fa[i])的贡献就是dis(i, fa[i]) * size[i] * (k -size[i])(注意dis表示的是虚树中的距离),然后考虑两两间的距离最值,我们对于每个点定义max[i], min[i]表示在i的子树中,以某一个关键点和i为端点的最大/最小距离,那么在更新的过程中我们首先可以用max[i] + dis(i, son[i]) + max[son[i]]更新答案,如果当前点是关键点,还可以直接用max[i]更新,min是一样的更新方式,所以对于关键点我们直接dfs一遍就可以求得对应的DP值了,直接输出f[root]就可以了,注意标记关键点的方法和边集的清空

Source:

/*
	created by scarlyw
*/
#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <vector>
#include <queue>
#include <set>

const int MAXN = 1000000 + 10;
const int INF = 1000000000;

std::vector<int> edge[MAXN], new_edge[MAXN];
int ind, n, x, y, k;
int dfn[MAXN], father[MAXN], size[MAXN], dep[MAXN], son[MAXN];
int top[MAXN];

inline void add_edge(int x, int y) {
	edge[x].push_back(y), edge[y].push_back(x);
}

inline void dfs1(int cur, int fa) {
	dfn[cur] = ++ind, father[cur] = fa, size[cur] = 1, dep[cur] = dep[fa] + 1;
	for (int p = 0; p < edge[cur].size(); ++p) {
		int v = edge[cur][p];
		if (v != fa) {
			dfs1(v, cur), size[cur] += size[v];
			if (size[v] > size[son[cur]]) son[cur] = v;
		}
	}
}

inline void dfs2(int cur, int tp) {
	top[cur] = tp;
	if (son[cur]) dfs2(son[cur], tp);
	for (int p = 0; p < edge[cur].size(); ++p) {
		int v = edge[cur][p];
		if (top[v] == 0) dfs2(v, v);
	}
}

inline int query_lca(int u, int v) {
	while (top[u] != top[v])
		(dep[top[u]] > dep[top[v]]) ? u = father[top[u]] : v = father[top[v]];
	return (dep[u] > dep[v]) ? v : u;
}

inline void solve_tree() {
	scanf("%d", &n);
	for (int i = 1; i < n; ++i) scanf("%d%d", &x, &y), add_edge(x, y);
	dfs1(1, 0), dfs2(1, 1);
}

inline bool comp(const int &a, const int &b) {
	return dfn[a] < dfn[b];
}

int now_size[MAXN], vis[MAXN], max[MAXN], min[MAXN];
long long f[MAXN];
int cnt, max_ans, min_ans;
inline void dfs(int cur) {
	if (new_edge[cur].size() == 0) {
		max[cur] = min[cur] = f[cur] = 0, now_size[cur] = 1;
		return ;
	}
	now_size[cur] = (vis[cur] == cnt), f[cur] = 0;
	max[cur] = -INF, min[cur] = INF;
	for (int p = 0; p < new_edge[cur].size(); ++p) {
		int v = new_edge[cur][p], d = dep[v] - dep[cur];
		dfs(v), f[cur] += f[v] + (long long)(k - now_size[v]) * now_size[v] * d;
		now_size[cur] += now_size[v];
		max_ans = std::max(max_ans, max[cur] + max[v] + d);
		max[cur] = std::max(max[v] + d, max[cur]);
		min_ans = std::min(min_ans, min[cur] + min[v] + d);
		min[cur] = std::min(min[v] + d, min[cur]);
	}
	if (vis[cur] == cnt) {
		max_ans = std::max(max_ans, max[cur]);
		min_ans = std::min(min_ans, min[cur]);
		min[cur] = 0;
	}
	new_edge[cur].clear();
}

inline void solve(int *a, int n) {
	static int stack[MAXN];
	int top = 0;
	stack[++top] = 1, ++cnt;
	for (int i = 1; i <= n; ++i) {
		int pre = query_lca(a[i], stack[top]);
		vis[a[i]] = cnt;
		while (true) {
			if (dep[stack[top - 1]] <= dep[pre]) {
				if (stack[top] == pre) break ;
				new_edge[pre].push_back(stack[top--]);
				if (pre != stack[top]) stack[++top] = pre;
				break ;
			}
			new_edge[stack[top - 1]].push_back(stack[top]), top--;
		}
		if (stack[top] != a[i]) stack[++top] = a[i];
	}
	while (top > 1) new_edge[stack[top - 1]].push_back(stack[top]), top--;
	max_ans = 0, min_ans = INF, dfs(1);
	printf("%lld %d %d\n", f[1], min_ans, max_ans);
}

int q;
int a[MAXN];
inline void solve_query() {
	scanf("%d", &q);
	for (int i = 1; i <= q; ++i) {
		scanf("%d", &k);
		for (int j = 1; j <= k; ++j) scanf("%d", &a[j]);
		std::sort(a + 1, a + k + 1, comp), solve(a, k);
	}
}

int main() {
	solve_tree();
	solve_query();
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值