[洛谷]P2279 [HNOI2003]消防局的設立 (#樹形dp)

題目描述

2020年,人類在火星上建立了一個龐大的基地羣,總共有n個基地。起初爲了節約材料,人類只修建了n-1條道路來連接這些基地,並且每兩個基地都能夠通過道路到達,所以所有的基地形成了一個巨大的樹狀結構。如果基地A到基地B至少要經過d條道路的話,我們稱基地A到基地B的距離爲d。

由於火星上非常乾燥,經常引發火災,人類決定在火星上修建若干個消防局。消防局只能修建在基地裏,每個消防局有能力撲滅與它距離不超過2的基地的火災。

你的任務是計算至少要修建多少個消防局才能夠確保火星上所有的基地在發生火災時,消防隊有能力及時撲滅火災。

輸入格式

輸入文件名爲input.txt。

輸入文件的第一行爲n (n<=1000),表示火星上基地的數目。接下來的n-1行每行有一個正整數,其中文件第i行的正整數爲a[i],表示從編號爲i的基地到編號爲a[i]的基地之間有一條道路,爲了更加簡潔的描述樹狀結構的基地羣,有a[i]<i。

輸出格式

輸出文件名爲output.txt

輸出文件僅有一個正整數,表示至少要設立多少個消防局纔有能力及時撲滅任何基地發生的火災。

輸入輸出樣例

輸入 #1複製

6
1
2
3
4
5

輸出 #1複製

2

思路

其實和P2899 [USACO08JAN]手機網絡有點類似,有興趣的同學可以去看一看,該題我也寫了題解。鏈接1 鏈接2

題目大意是給出一個樹,一個消防站能夠覆蓋與他距離≤2的點。求覆蓋整個樹的所有節點至少需要多少個消防站。

樹形dp的常規操作是每個節點都可以成爲根節點,然後在這個節點搜索它的子樹獲取最優解。這個我在P2016那題說過。我們根據P2899那題的經驗,對一顆子樹劃分爲5個狀態。

令dp[i][0/1/2/3/4]爲節點i及其節點i的子樹中全部被覆蓋所需的最小消防局數。dp[i][0]爲節點i父親的父親及以下節點完全被覆蓋所需的最小代價,dp[i][1]爲節點i的父親及以下節點完全被覆蓋所需的最小代價,dp[i][2]爲節點i及以下節點完全被覆蓋所需要的最小代價。顯然,這3種情況是節點i一定被覆蓋的情況。

dp[i][3]爲節點i的兒子及以下節點完全被覆蓋所需最小代價,dp[i][4]爲節點i兒子的兒子及以下節點完全被覆蓋所需的最小代價。顯然,這2種情況是節點i不一定被覆蓋的情況。是不是和P2899迷之相似?這樣感覺就對了!

簡段潔說,dp[i][0]:選i爲消防局;dp[i][1]:至少選i的一個兒子爲消防局;dp[i][2]:至少選i的一個孫子爲消防局。這3種狀態顯然節點i被覆蓋。dp[i][3]:選i的所有兒子被覆蓋;dp[i][4]:選i的所有孫子被覆蓋。這2種狀態節點i不一定被覆蓋。

1.1.dp[i][0]

因爲節點i有消防局,所以孫子會被覆蓋到。要求孫子全部被覆蓋,兒子0-4的狀態都滿足。所以:

dp[i][0]=min(Σdp[son][0...4])+1

這裏+1是因爲本身放了一個消防局。

1.2.dp[i][1]

​選了兒子中的一個節點,所以其他兒子也會被覆蓋到。但是其他兒子的兒子無法被覆蓋到。所以在轉移時,選取一個點放消防站,並要求i的兒子被覆蓋到。

dp[i][1]=min(dp[son1][0]+Σ(son1≠son2)min(dp[son2][0...3]))

1.3.dp[i][2]

同理有:

dp[i][2]=min(dp[son1][1]+Σ(son1≠son2)min(dp[son2][0...2]))

1.4.dp[i][3]

要讓節點i的兒子都被覆蓋,就是讓它們本身都覆蓋。

dp[i][3]=Σdp[son][0...2]

1.5.dp[i][4]

同理有:

dp[i][4]=Σdp[son][0...3]

推好了!可是......特別的亂!多!煩!那就看能不能合併吧。

2.1.dp[i][0],dp[i][3],dp[i][4]

dp[i][0]可以從4個兒子節點的狀態裏得到。但顯然dp[son][4]是最優的。因爲dp[son][4]=Σdp[grandson][0...3]。

所以:

dp[i][0]=Σdp[son][4]

同理有:

dp[i][3]=Σdp[son][2]

dp[i][4]=Σdp[son][3]

狀態0,3,4已經優化完成,事實上我們還可以利用它們進行優化狀態1,2。

2.2.dp[i][1],dp[i][2]

dp[i][1]=min(dp[son1][0]+Σ(son1!=son2)dp[son2][0...3])

=dp[i][4]+min(dp[son1][0]-dp[son1][3])

同理有:

dp[i][2]=min(dp[son1][1]+Σ(son1≠son2)min(dp[son2][0...2]))

=dp[i][3]+min(dp[son1][2]-dp[son1][1])

#include <stdio.h>
#include <iostream>
#define inf 2e9+7
#define N 1001
using namespace std;
int n,cnt,head[N],s,dp[N][5];
struct node
{
	int to,nxt;
}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)
{
	dp[u][1]=inf;
	dp[u][2]=inf;
	register int j,i;
	for(i=head[u];i;i=e[i].nxt)
	{
		int v(e[i].to);
		if(v==fa) continue;
		dfs(v,u);
		dp[u][0]+=dp[v][4];
		dp[u][3]+=dp[v][2];
		dp[u][4]+=dp[v][3];
		dp[u][2]=min(dp[u][2],dp[v][1]-dp[v][2]);
		dp[u][1]=min(dp[u][1],dp[v][0]-dp[v][3]);
		
	}
	dp[u][0]++;
	dp[u][2]+=dp[u][3];
	dp[u][1]+=dp[u][4];
	dp[u][1]=min(dp[u][1],dp[u][0]);
	dp[u][2]=min(dp[u][2],dp[u][1]);
	dp[u][3]=min(dp[u][3],dp[u][2]);
	dp[u][4]=min(dp[u][4],dp[u][3]);
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register int i,j;
	cin>>n;
	for(i=2;i<=n;i++)
	{
		int u;
		cin>>u;
		add(u,i);
		add(i,u);
	}
	dfs(1,-1);
	cout<<dp[1][2]<<endl;
	return 0;
}

 

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