1. 程式人生 > 實用技巧 >[題解]逐個擊破 (動態規劃20題(1/20))

[題解]逐個擊破 (動態規劃20題(1/20))

前言

寫在第一篇題解裡。
經考試測定,本人動態規劃很弱,下定在過2021春節之前做20道動態規劃題,並完成對於每一道題的題解。

[題解]逐個擊破

題目TP門

題目背景

三大戰役的平津戰場上,傅作義集團在以北平、天津為中心,東起唐山西至張家口的鐵路線上擺起子一字長蛇陣,並企圖在潰敗時從海上南逃或向西逃竄。為了就地殲敵不讓其逃走,毛主席制定了先切斷敵人東西兩頭退路然後再逐個殲滅敵人的戰略方針。秉承偉大軍事家的戰略思想,作為一個有智慧的軍長你,遇到了一個類似的戰場局面。

題目描述

現在有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]) 中。
分兩種情況進行狀態轉移:

  1. 若該節點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)。
  2. 若該節點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
inline void Quick_Read(LL &N) {
	N = 0;
	char c = getchar();
	LL op = 1LL;
	while(c < '0' || c > '9') {
		if(op == '-')
			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;
}