给出一棵nn个节点的带权树,有mm个军队在某些结点上,使这个结点的子树都被覆盖.可以移动军队到某个结点(不能为根节点),要求让所有叶子节点都被覆盖.最小化移动军队用时的最大值.

n,m50000n,m\le 50000

Luogu P1084

Solution

很显然考虑二分答案

设当前答案为ansans,考虑如何checkcheck

可以发现,每个军队一定都会贪心地尽量往上提.因为越往上覆盖范围越广,且包含下面的范围.

那么如果某个军队到根节点之后还能继续走,那么就有可能走到根节点的其他某个儿子上(再向下走肯定不优)

处理出两个数组:Rest[]Rest[]存走到根节点后还能继续走的那一部分的长度,Left[]Left[]存目前还不能被覆盖的根节点的儿子节点到根的距离

此时,我们就是需要判断用Rest[]Rest[]是否能够覆盖Left[]Left[]

排个序贪心选即可

注意: 具体实现上有点麻烦.对于某一个在Rest中的点,它不一定是覆盖根节点当前的这个儿子(即最后它所处的位置不一定在现在到根的那条路径上),因为它可能可以覆盖其他儿子使得策略更优

这道题乍一看好像挺简单,结果调了我一天,最后发现是最后一步贪心有问题.......

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
150
151
152
153
154
155
156
157
158
159
160
#include <bits/stdc++.h>

#define x first
#define y second
#define y1 Y1
#define y2 Y2
#define mp make_pair
#define pb push_back

using namespace std;

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

template <typename T> inline int Chkmax (T &a, T b) { return a < b ? a = b, 1 : 0; }
template <typename T> inline int Chkmin (T &a, T b) { return a > b ? a = b, 1 : 0; }

inline void proc_status()
{
ifstream t ("/proc/self/status");
cerr << string (istreambuf_iterator <char> (t), istreambuf_iterator <char> ()) <<endl;
}

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

const int Maxn = 50000 + 100;

int N, M;
int e, Begin[Maxn], To[Maxn << 1], Next[Maxn << 1], W[Maxn << 1];
int A[Maxn], r_sum;
int dep[Maxn], anc[22][Maxn];

inline void add_edge (int x, int y, int z) { To[++e] = y; Next[e] = Begin[x]; Begin[x] = e; W[e] = z; }

inline void dfs_pre (int x, int f)
{
for (int i = 1; i <= 20; ++i) anc[i][x] = anc[i - 1][anc[i - 1][x]];
for (int i = Begin[x]; i; i = Next[i])
{
int y = To[i];
if (y == f) continue;
anc[0][y] = x;
dep[y] = dep[x] + W[i];
dfs_pre (y, x);
}
}

vector <pii> Rest, Left;

int Vis[Maxn];

inline void dfs (int x, int f)
{
if (Vis[x]) return ;
int tmp = 1, fl = 0;
for (int i = Begin[x]; i; i = Next[i])
{
int y = To[i];
if (y == f) continue;
fl = 1;
dfs(y, x);
tmp &= Vis[y];
}
if (fl) Vis[x] = tmp;
}

inline int Check (int now_ans)
{
Rest.clear(), Left.clear();
for (int i = 1; i <= N; ++i) Vis[i] = 0;
for (int i = 1; i <= M; ++i)
{
int tmp = A[i];
if (now_ans < dep[tmp])
{
int x = tmp, now = now_ans;
for (int j = 20; j >= 0; --j)
{
int fa = anc[j][x];
if (fa && dep[x] - dep[fa] <= now)
{
now -= dep[x] - dep[fa];
x = fa;
}
}
Vis[x] = 1;
}
}
dfs(1, 0);
for (int i = 1; i <= M; ++i)
{
int tmp = A[i];
if (now_ans >= dep[tmp])
{
int x = tmp;
for (int j = 20; j >= 0; --j) if (anc[j][x] > 1) x = anc[j][x];
Rest.pb(mp(now_ans - dep[tmp], x));
}
}
for (int i = Begin[1]; i; i = Next[i])
{
int x = To[i];
if (!Vis[x]) Left.pb(mp(W[i], x));
}
sort(Rest.begin(), Rest.end());
sort(Left.begin(), Left.end());
int j = 0;
if (!Left.size()) return 1;
for (int i = 0; i < Rest.size(); ++i)
{
if (!Vis[Rest[i].y]) Vis[Rest[i].y] = 1;
else if (Rest[i].x >= Left[j].x) Vis[Left[j].y] = 1;
while (j < Left.size() && Vis[Left[j].y]) ++j;
}
return j >= Left.size();
}

inline void Solve ()
{
dfs_pre(1, 0);
int l = 0, r = r_sum, ans = -1;
while (l <= r)
{
int mid = l + r >> 1;
if (Check(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
cout<<ans<<endl;
}

inline void Input ()
{
N = read();
for (int i = 1; i < N; ++i)
{
int x = read(), y = read(), z = read();
add_edge (x, y, z);
add_edge (y, x, z);
r_sum += z;
}
M = read();
for (int i = 1; i <= M; ++i) A[i] = read();
}

int main()
{
#ifndef ONLINE_JUDGE
freopen("blockade.in", "r", stdin);
freopen("blockade.out", "w", stdout);
#endif
Input();
Solve();
return 0;
}