题解-luogu-p2495消耗战

题目链接

现在我随机@一个人 这个人必须帮我写题目概括

@xj

这是一篇虚树入门题解。

考虑题目中 $m=1$ 的情况。树上动规,定义状态 $f[u]$ 表示切断节点 $u$ 与该子树内所有关键点的路径,最小总代价。状态转移方程如下:
$$
f[u]=
\begin{equation}
\begin{cases}
w(u,v) \ \texttt{if h[v]=1} \
\min(w(u,v),f[v])\ \texttt{if h[v]=0}
\end{cases}
\end{equation}
$$
动态规划部分不再赘述。该算法复杂度为 $O(n)$ 。

考虑题目中 $m\not= 1$ 的情况。如果对于每一次查询,都进行一次 $O(n)$ 复杂度的遍历显然无法接受。观察到题目中 $\Sigma{k}$ 的取值不大,可以考虑针对没个询问,舍弃树上的一些非关键点,仅保留一棵包含原树上部分节点的虚树,并在虚树上进行动态规划。

那么,在虚树上应该保留原树上的哪些点呢?

首先,每次讯问中给出的 $k$ 个关键点(资源丰富的岛屿)显然应该包含在虚树中。其次,任意两个关键点的最近公共祖先也应该包含在虚树中;因为在本题中,切断一条边可以同时切断根节点与多个关键点间的路径,最近公共祖先的存在为动态规划提供了这种状态转移。最后为了方便,我们可以将 $1$ 号节点(即根节点)也加入到虚树中。

构造虚树的方法很多,在这里介绍一种用建树的算法流程。

令 $1$ 号节点为虚树的根。

将所有关键点按照其在原树中的 dfs 序升序排序。假设当前正在处理的关键点为 $u$ 。

维护一个栈,使得栈底到栈顶的元素依次为虚树上从根节点到节点 $u$ 的一条链

这里为什么要维护一个栈呢?

1.jpg

如图:在处理完 $3$ 号关键点后,虚树中只有 $1$ 、 $3$ 两个节点,栈中的元素依次为 $1$ 、 $3$ 。但是这条链是不完整的,可以观察到在处理 $4$ 号关键点时,还需要将 $2$ 号节点添加到虚树中。利用栈的性质,我们可以动态维护一条虚树上的链,并在必要的时候添加节点。

回到刚才的叙述,当前正在处理关键点 $u$ 。根据栈的定义,上一个处理的关键点一定为 $stack.top()$ 。

由于进行过排序,即节点 $u$ 的 dfs 序大于上一个关键点的 dfs 序,因此节点 $u$ 要么是上一个关键点的后代,要么与其没有祖先-后代的关系。

显然,如果节点 $u$ 是 $stack.top()$ 的后代,那么只需将节点 $u$ 入栈即可,因为 $u$ 在虚树中,一定是上一个关键点的儿子。

但是如果节点 $u$ 与 $stack.top()$ 没有祖先-后代的关系,那么此时的讨论将比较复杂。

可以结合上图观察,假设当前正在处理 $4$ 号关键点。我们可以首先将栈顶弹出,因为 $stack.top()$ 一定不在根节点到节点 $u$ 的链上。此时,栈中剩余的元素只有 $1$ 。然而, $3$ 与 $4$ 的最近公共祖先 $2$ 号节点还不在栈中;因此我们需要把 $2$ 号节点入栈,并将刚刚弹出的节点与新的栈顶在虚树中连边。处理结束后,将 $4$ 入栈。

接下来处理 $5$ 号关键点,此时栈中的元素依次为 $1$ 、$2$ 、$4$ 。首先将栈顶弹出,但由于我们接下来需要维护的链为 $1->5$ ,栈中仍然有节点 $2$ ,因此我们需要将 $2$ 和刚刚弹出的节点 $4$ 连边,并且重复以上操作。将新的栈顶 $2$ 弹出后,栈中只剩下节点 $1$ 。这时发现 $1$ 号节点恰好为 $5$ 与上一次处理的关键点 $4$ 的最近公共祖先,因此将 $1$ 与 $2$ 连边后,弹栈可以中止了。处理结束后,将 $5$ 入栈。

此时我们已经处理完了所有关键点,但是栈中的元素间还没有连边。将栈中的节点依次连边后,虚树的构建就完成了。

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
将关键点按照 dfs 序排序
stack.push(1)
for u = 1 ~ k: // 假设当前正在处理节点 u
lca = Lca(u, stack.top())
while stack.top() != lca:
tmp = stack.top()
stack.pop()
if dfn[stack.top()] < dfn[lca]
stack.push(lca)
AddEdge(stack.top(), tmp)
stack.push(u)
while stack.top() != 1:
tmp = stack.top()
stack.pop()
AddEdge(stack.top(), tmp)

可以证明,对于本题,虚树上的边权一定对应原树上两节点之间边权的最小值。证明不再赘述。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Edge {int v;ll w;Edge(int a, ll b) {v = a, w = b;}};
struct Key {int u, dfn;}keys[250005];
int keys_cnt;
inline bool cmp(Key a, Key b) {
return a.dfn < b.dfn;
}
int N, M;
vector<Edge> G[250005], VT[250005];
int f[250005][19], g[250005][19], dep[250005];
ll d[250005];
bool h[250005];
int dfn[250005], dfn_idx;
int lg[250005];

void dfs(int u, int fa, ll w) {
dep[u] = dep[fa] + 1;
dfn[u] = ++dfn_idx;
f[u][0] = fa, g[u][0] = w;
for (int i = 1; i <= 18; ++i)
f[u][i] = f[f[u][i - 1]][i - 1],
g[u][i] = min(g[f[u][i - 1]][i - 1], g[u][i - 1]);
for (vector<Edge>::iterator it = G[u].begin(); it != G[u].end(); it++) {
int v = it -> v;
ll w = it -> w;
if (v == fa) continue;
dfs(v, u, w);
}
}

void dp(int u) {
for (vector<Edge>::iterator it = VT[u].begin(); it != VT[u].end(); it++) {
int v = it -> v;
ll w = it -> w;
dp(v);
if (h[v]) d[u] += w;
else d[u] += min(w, d[v]);
h[v] = 0; d[v] = 0;
}
VT[u].clear();
}

inline int Lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
while (dep[u] > dep[v]) {
u = f[u][lg[dep[u] - dep[v]]];
}
if (u == v) return u;
for (int i = lg[dep[u]]; i >= 0; --i)
if (f[u][i] != f[v][i])
u = f[u][i], v = f[v][i];
return f[u][0];
}

inline int query(int u, int v) {
int ans = 0x3f3f3f3f;
while (dep[u] > dep[v]) {
ans = min(ans, g[u][lg[dep[u] - dep[v]]]);
u = f[u][lg[dep[u] - dep[v]]];
}
return ans;
}

inline void AddEdge(int u, int v) {
int w = query(v, u);
VT[u].push_back(Edge(v, w));
}

int main() {
for (register int i = 2; i <= 250000; ++i)
lg[i] = lg[i >> 1] + 1;
memset(g, 0x3f, sizeof(g));
scanf("%d", &N);
for (register int i = 1; i < N; ++i) {
int u, v;
ll w;
scanf("%d%d%lld", &u, &v, &w);
G[u].push_back(Edge(v, w));
G[v].push_back(Edge(u, w));
}
dfs(1, 0, 0);
scanf("%d", &M);
while (M--) {
int k;
keys_cnt = 0;
scanf("%d", &k);
for (register int i = 1; i <= k; ++i) {
scanf("%d", &keys[++keys_cnt].u);
h[keys[keys_cnt].u] = 1;
keys[keys_cnt].dfn = dfn[keys[keys_cnt].u];
}
stack<int> s;
sort(keys + 1, keys + keys_cnt + 1);
s.push(1);
for (register int i = 1; i <= keys_cnt; ++i) {
int u = keys[i].u;
int lca = Lca(u, s.top());
while (s.top() != lca) {
int tmp = s.top(); s.pop();
if (dfn[s.top()] < dfn[lca])
s.push(lca);
AddEdge(s.top(), tmp);
}
s.push(u);
}
while (s.top() != 1) {
int tmp = s.top(); s.pop();
AddEdge(s.top(), tmp);
}
dp(1);
printf("%lld\n", d[1]);
d[1] = 0;
}
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×