[洛谷]P2458 [SDOI2006]保安站崗 (#樹形dp)

題目描述

五一來臨,某地下超市爲了便於疏通和指揮密集的人員和車輛,以免造成超市內的混亂和擁擠,準備臨時從外單位調用部分保安來維持交通秩序。

已知整個地下超市的所有通道呈一棵樹的形狀;某些通道之間可以互相望見。總經理要求所有通道的每個端點(樹的頂點)都要有人全天候看守,在不同的通道端點安排保安所需的費用不同。

一個保安一旦站在某個通道的其中一個端點,那麼他除了能看守住他所站的那個端點,也能看到這個通道的另一個端點,所以一個保安可能同時能看守住多個端點(樹的結點),因此沒有必要在每個通道的端點都安排保安。

編程任務:

請你幫助超市經理策劃安排,在能看守全部通道端點的前提下,使得花費的經費最少。

輸入格式

第1行 n,表示樹中結點的數目。

第2行至第n+1行,每行描述每個通道端點的信息,依次爲:該結點標號i(0<i<=n),在該結點安置保安所需的經費k(<=10000),該邊的兒子數m,接下來m個數,分別是這個節點的m個兒子的標號r1,r2,...,rm。

對於一個n(0 < n <= 1500)個結點的樹,結點標號在1到n之間,且標號不重複。

輸出格式

最少的經費。

如右圖的輸入數據示例

輸出數據示例:

輸入輸出樣例

輸入 #1複製

6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0

輸出 #1複製

25

說明/提示

樣例說明:在結點2,3,4安置3個保安能看守所有的6個結點,需要的經費最小:25


思路

我感覺和P2899 手機網絡是一模一樣的題目......相比之下,只需要改2處代碼......(雙倍經驗)對於P2899那道題,我已經寫了詳細的題解。P2899的題解:這篇文章

首先對於每個節點i,都有如下3種選擇:
不選自己,選兒子;
不選自己,選父親;
我自己選我自己。

令dp[i][0/1/2]爲節點i及其節點i的子樹中全部被覆蓋所需的最小保安數,dp[i][0]表示點i有保安,dp[i][1]爲點i沒有保安,也就是就是父親有信號塔,dp[i][2]表示節點i被間接管轄,也就是兒子有保安。

1.dp[i][0]

因爲節點i有保安,所以對於i的子節點son,它可以沒有保安,也可以有保安,也可以從son的子節點轉移過來。因此:

dp[i][0]=∑min(dp[son][1],dp[son][0],dp[son][2])+val[i]

這裏+val[i]是因爲自己本身放置了一個保安,產生的代價。

2.dp[i][1]

因爲節點i沒有保安,靠的是節點i的父親才被覆蓋的,所以對於節點i的子節點son是不可能選父親的。所以dp[i][1]可以從dp[son][0]轉移過來(子節點有保安),也可以從dp[son][2]轉移過來(子節點的兒子有保安)。

dp[i][1]=∑min(dp[son][0[,dp[son][2])

3.dp[i][2]

因爲節點i沒有保安,靠它兒子,所以節點i的子節點son也不可能選父親,必然可以從dp[son][0]轉移過來,也可以從dp[son][2]轉移過來。

dp[i][2]=∑min(dp[son][0],dp[son][2])

很快會發現,如果的確是這樣的話,如果dp[i][2]從dp[son][2]轉移過來,也就是若恆有dp[son][2] ≤ dp[son][0],就意味着節點i的所有子節點都沒有保安!那不就涼了)怎麼辦?

所以我們要設立一個反悔機制我只需要一個兒子選。只需要用p來記錄每一次dp[son][0]-min(dp[son][2],dp[son][0]),這樣就保證,就算恆有dp[son][2] ≤ dp[son][0],也一定有一個是兒子選了的,如果不選dp[son][2],最後p的狀態也爲0。若不信,我們做個推導:

記p=min(p,dp[son][0]-min(dp[son][0],dp[son][2]))

dp[i][2]=min(dp[son][0],dp[son][2])+p

=min(dp[son][0],dp[son][2])+dp[son][0]-min(dp[son][0],dp[son][2])

若dp[son][2] ≤ dp[son][0]恆成立(注意我說的是恆成立,意思是說dp[i][2]全是從dp[son][2]轉移過來)

原式=dp[son][2]+dp[son][0]-dp[son][2]

=dp[son][0]

也就是把dp[son][2]強制轉換成了dp[son][0]!

若dp[son][2]>dp[son][0]

原式=dp[son][0]+dp[son][0]-dp[son][0]

=dp[son][0]

也就是說並不影響dp[son][0]的正常取值!

最後答案爲min(dp[root][0],dp[root][2])。

#include <stdio.h>
#include <iostream>
#define inf 2e9+7
#define N 2001
using namespace std;
int n,m,s,dp[N][3],head[N],cnt,val[N];
struct node
{
	int nxt,to;
}e[N<<1];
inline void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa)
{
	register int i,p(inf);
	dp[u][0]=val[u];
	for(i=head[u];i;i=e[i].nxt)
	{
		int v(e[i].to);
		if(v==fa) continue;
		dfs(v,u);
		dp[u][0]+=min(dp[v][0],min(dp[v][1],dp[v][2]));
		dp[u][1]+=min(dp[v][0],dp[v][2]);
		dp[u][2]+=min(dp[v][0],dp[v][2]);
		p=min(p,dp[v][0]-min(dp[v][2],dp[v][0]));//表示其它兒子的總和
	}
	dp[u][2]+=p;//建立反悔機制 
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register int i,j,k;
	cin>>n;
	for(i=1;i<=n;i++)
	{
		int u,v;
		cin>>u;
		cin>>val[u]>>m;
		while(m--)
		{
			cin>>v;
			add(u,v);
			add(v,u);
		}
	}
	dfs(1,-1);
	cout<<min(dp[1][0],dp[1][2])<<endl;
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章