題目描述
五一來臨,某地下超市爲了便於疏通和指揮密集的人員和車輛,以免造成超市內的混亂和擁擠,準備臨時從外單位調用部分保安來維持交通秩序。
已知整個地下超市的所有通道呈一棵樹的形狀;某些通道之間可以互相望見。總經理要求所有通道的每個端點(樹的頂點)都要有人全天候看守,在不同的通道端點安排保安所需的費用不同。
一個保安一旦站在某個通道的其中一個端點,那麼他除了能看守住他所站的那個端點,也能看到這個通道的另一個端點,所以一個保安可能同時能看守住多個端點(樹的結點),因此沒有必要在每個通道的端點都安排保安。
編程任務:
請你幫助超市經理策劃安排,在能看守全部通道端點的前提下,使得花費的經費最少。
輸入格式
第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;
}