[题解] 逐个击破
[题解]逐个击破
题目背景
三大战役的平津战场上,傅作义集团在以北平、天津为中心,东起唐山西至张家口的铁路线上摆起子一字长蛇阵,并企图在溃败时从海上南逃或向西逃窜。为了就地歼敌不让其逃走,毛主席制定了先切断敌人东西两头退路然后再逐个歼灭敌人的战略方针。秉承伟大军事家的战略思想,作为一个有智慧的军长你,遇到了一个类似的战场局面。
题目描述
现在有N个城市,其中K个被敌方军团占领了,N个城市间有N-1条公路相连,破坏其中某条公路的代价是已知的,现在,告诉你K个敌方军团所在的城市,以及所有公路破坏的代价,请你算出花费最少的代价将这K个地方军团互相隔离开,以便第二步逐个击破敌人。
输入格式
第一行包含两个正整数n和k。
第二行包含k个整数,表示哪个城市别敌军占领。
接下来n-1行,每行包含三个正整数a,b,c,表示从a城市到b城市有一条公路,以及破坏的代价c。城市的编号从0开始。
输出格式
输出一行一个整数,表示最少花费的代价。
输入输出样例
输入
5 3
1 2 4
1 0 4
1 3 8
2 1 1
2 4 3
输出
4
说明/提示
【数据范围】
10%的数据:2≤n≤10;
100%的数据:2≤n≤100000,2≤k≤n,1≤c≤1000000。
思路
一道树形DP。
先来定义几个变量:Foe[i]:i节点是否为敌军的驻扎地,Hav[i]:以i为根节点的子树中时候有敌军。
状态转移过程:
dp[i][1]:以i节点为根节点的子树中有敌军且合法的最小代价
dp[i][0]:以i节点为根节点的子树中没有敌军且合法的最小代价
不难发现,结果存在 min(dp[root][1],dp[root][0]) 中。
分两种情况进行状态转移:
- 若该节点u被敌军驻扎,则以该节点为根节点的子树比会有敌军,所以dp[u][0]必不可能成立,则把dp[u][0]置为INF。设该节点u的儿子为v,若v被敌军驻扎,则u,v这条边必须被割断,否则将会不合法。若v没有敌军驻扎,则加上min(dp[v][0], dp[v][1] + dist(u,v))这条代价。dp[v][0]则表示以v为根节点的子树没有敌人的代价,不需要再割了。而dp[v][1] + dist(u,v)表示以v为根节点的子树没有敌人的代价,必须割掉u,v这条边,所以代价为dp[v][1] + dist(u,v)。
- 若该节点u没有被敌军驻扎,则dp[u][0]可以按照转换为第一种情况dp[u][1]来求出。而dp[u][1]可以先转换为dp[u][0]来求,最后看删除哪条边为最小的情况,所以使用min(dp[u][1], dp[u][0] - min(dp[v][0], dp[v][1] + dist(u,v)) + dp[v][1])来更新dp[u][1]的值。
按照上述方法跑一边树形DP即可。
C++代码
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 1e15
#define LL long long
void Quick_Read(LL &N) {
N = 0;
char c = getchar();
LL op = 1LL;
while(c < '0' || c > '9') {
if(c == '-')
op = -1LL;
c = getchar();
}
while(c >= '0' && c <= '9') {
N = (N << 1LL) + (N << 3LL) + c - 48LL;
c = getchar();
}
N *= op;
}
struct Node {
LL to, dist;
Node() { to = dist = 0; }
Node(LL T, LL D) {
to = T;
dist = D;
}
};
const LL MAXN = 1e5 + 5;
vector<Node> v[MAXN];
bool Hav[MAXN], Foe[MAXN];
LL dp[MAXN][2];
LL n, k;
inline void Read() {
LL A, B, C;
Quick_Read(n);
Quick_Read(k);
for(LL i = 1; i <= k; i++) {
Quick_Read(A);
A++;
Foe[A] = true;
}
for(LL i = 1; i < n; i++) {
Quick_Read(A);
Quick_Read(B);
Quick_Read(C);
A++; B++;
v[A].push_back(Node(B, C));
v[B].push_back(Node(A, C));
}
}
inline void DP(LL now, LL father) {
LL SIZ = v[now].size();
Hav[now] = Foe[now];
LL Sum = 0;
for(LL i = 0; i < SIZ; i++) {
LL next = v[now][i].to;
if(next == father)
continue;
DP(next, now);
Hav[now] |= Hav[next];
Sum += min(dp[next][0], dp[next][1] + v[now][i].dist);
}
if(Foe[now]) {
dp[now][0] = INF;
for(LL i = 0; i < SIZ; i++) {
LL next = v[now][i].to;
if(next == father || (!Hav[next]))
continue;
if(Foe[next])
dp[now][1] += dp[next][1] + v[now][i].dist;
else
dp[now][1] += min(dp[next][0], dp[next][1] + v[now][i].dist);
}
}
else {
dp[now][1] = Sum;
for(LL i = 0; i < SIZ; i++) {
LL next = v[now][i].to;
if(next == father || (!Hav[next]))
continue;
dp[now][1] = min(dp[now][1], Sum - min(dp[next][0], dp[next][1] + v[now][i].dist) + dp[next][1]);
if(Foe[next])
dp[now][0] += dp[next][1] + v[now][i].dist;
else
dp[now][0] += min(dp[next][0], dp[next][1] + v[now][i].dist);
}
}
}
int main() {
Read();
DP(1LL, -1LL);
printf("%lld", min(dp[1][0], dp[1][1]));
return 0;
}