大工程
题目背景:
分析:虚树 + 树型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;
}