[BZOJ-2286]消耗战

Description

给你一棵n个点的树,每条边上有边权。
有m次询问,每次询问都给出一些关键点。你要割掉一些边,使得从1出发无法到达这些关键点。
求割掉边的边权总和最小值。
n ≤ 250000, m ≥ 1, Sigma(关键点个数) ≤ 500000

Solution

这是一道[SDOI2011]的题。
首先我们考虑树形DP,F[u]表示从u出发到不了u的子树中所有关键点的最小代价。
那么如果u的儿子v是关键点,那么F[u] += Dis(u, v)——(u, v)这条边一定得割
否则F[u] += min(F[v], Dis(u, v))
这样我们得到了一个O(n)的DP,但询问m次后时间复杂度就很大了。
所以我们需要用虚树处理,这样总时间复杂度就是O(关键点个数)了。

Notice

因为这道题是树形DP,只要从下向上转移即可,所以不用把虚树边练出来,只要记录每个关键点及LCA在虚树中的父亲即可。
还要倍增预处理祖先和到祖先边权的最小值。

Code

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define sqz main
#define ll long long
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define Rep(i, a, b) for (int i = (a); i < (b); i++)
#define travel(i, u) for (int i = head[u]; ~i; i = edge[i].next)

const ll INF = 1e9, Mo = 998244353;
const int N = 250000;
const double eps = 1e-6;
namespace slow_IO
{
ll read()
{
ll x = 0; int zf = 1; char ch = getchar();
while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if (ch == '-') zf = -1, ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x * zf;
}
void write(ll y)
{
if (y < 0) putchar('-'), y = -y;
if (y > 9) write(y / 10);
putchar(y % 10 + '0');
}
}
using namespace slow_IO;

int n, num, tmp, edgenum = 0, Time = 0, top;
int head[N + 5], X[N + 5], Y[N + 5], dfn[N + 5], Deep[N + 5], fa[N + 5], Fa[N + 5][20], Dis[N + 5][20], stack[N + 5], flag[N + 5];
ll Ans[N + 5];
struct node
{
int vet, val, next;
}edge[2 * N + 5];
void addedge(int u, int v, int w)
{
edge[edgenum].vet = v;
edge[edgenum].val = w;
edge[edgenum].next = head[u];
head[u] = edgenum++;
}

void dfs(int u, int fa)
{
dfn[u] = ++Time;
Deep[u] = Deep[fa] + 1;
travel(i, u)
{
int v = edge[i].vet;
if (v == fa) continue;
Fa[v][0] = u, Dis[v][0] = edge[i].val;
dfs(v, u);
}
}
void Prepare()
{
rep(i, 1, 18)
rep(j, 1, n)
{
Fa[j][i] = Fa[Fa[j][i - 1]][i - 1];
Dis[j][i] = min(Dis[j][i - 1], Dis[Fa[j][i - 1]][i - 1]);
}
}

int lca(int u, int v)
{
if (Deep[u] < Deep[v]) swap(u, v);
per(i, 18, 0)
if (Deep[Fa[u][i]] >= Deep[v]) u = Fa[u][i];
if (u == v) return u;
per(i, 18, 0)
if (Fa[u][i] != Fa[v][i]) u = Fa[u][i], v = Fa[v][i];
return Fa[u][0];
}
int dis(int u, int v)
{
int ans = INF;
if (Deep[u] < Deep[v]) swap(u, v);
per(i, 18, 0)
if (Deep[Fa[u][i]] >= Deep[v]) ans = min(ans, Dis[u][i]), u = Fa[u][i];
return ans;
}

int cmp(int x, int y)
{
return dfn[x] < dfn[y];
}
void Build()
{
sort(X + 1, X + num + 1, cmp);
stack[top = 1] = X[1];
rep(i, 2, num)
{
int u = lca(X[i], stack[top]);
while (top && Deep[stack[top]] > Deep[u])
{
fa[stack[top]] = Deep[stack[top - 1]] < Deep[u] ? u : stack[top - 1];
top--;
}
if (!top || u != stack[top])
{
X[++tmp] = u;
stack[++top] = u;
}
stack[++top] = X[i];
}
while (--top) fa[stack[top + 1]] = stack[top];
}
ll solve()
{
sort(X + 1, X + tmp + 1, cmp);
rep(i, 1, tmp) Ans[X[i]] = 0;
per(i, tmp, 2)
{
if (flag[X[i]]) Ans[fa[X[i]]] += (ll)dis(X[i], fa[X[i]]);
else Ans[fa[X[i]]] += min((ll)dis(X[i], fa[X[i]]), Ans[X[i]]);
}
return Ans[1];
}

int sqz()
{
n = read();
rep(i, 1, n) head[i] = -1;
Rep(i, 1, n)
{
int u = read(), v = read(), w = read();
addedge(u, v, w), addedge(v, u, w);
}
dfs(1, 0);
Prepare();
int m = read();
while (m--)
{
tmp = num = read() + 1, X[1] = 1;
rep(i, 2, num) X[i] = Y[i] = read(), flag[X[i]] = 1;
Build();
printf("%lld\n", solve());
rep(i, 2, num) flag[Y[i]] = 0;
}
}
文章目录
  1. 1. Description
  2. 2. Solution
  3. 3. Notice
  4. 4. Code