洛谷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\)
根據題目描述很容易想到是樹上\(DP\),但是有很多需要考慮到的東西:
首先容易被坑的一點:
- 很可能會直接想到兩種狀態:當前點占用和不占用。
- 在這種情況下會很容易就作出如下的判斷
- \(F[u][1]+=min\{F[v][0],F[v][1]\};\)(當前點占用的轉移)
- \(F[u][0]+=F[v][1];\)(當前點不占用的轉移)
然後就會第一時間\(w\)\(\bar a\)掉這個題目。
為什麽呢?因為這個轉移中,我們忽略了子節點對父節點的影響,子節點的選中也可以控制父節點,所以我們的狀態應該有三個:
- 當前點被占用(子節點選啥都可以,直接取最小值)
- 當前點沒有被占用(父節點占用並控制該點,所以可以把子節點裏面已經獨立可用的都挖過來)
- 當前點沒有被占用,但是子節點中存在有已經被占用的點
- 這個轉移相對不是很好考慮,怎麽確認子節點中誰被占用呢?
- 我們先考慮不管子節點有沒有被占用的,直接先取兩種轉移狀態最小值臨時存起來
- 然後一一比較,看哪一個子節點變成占用態消耗最少,答案取min
- 復雜度\(O(n)\)
實際上算是樹上\(DP\)的一種常用套路吧,不過本蒻以前沒有掌握,被這個題目好好教育了一下。
Code:
#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 1510
#define INF 0x3f3f3f3f
using namespace std;
int cnt,head[MAXN],deep[MAXN];
int n,vis[MAXN],arr[MAXN],size[MAXN],f[MAXN][4];
struct edge{
int nxt;
int to;
}e[MAXN<<1];
void dfs(int u,int fa){
f[u][1]=arr[u];//預先處理被占用的情況
int sum=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fa){
//對子樹的處理
dfs(v,u);
f[u][1]+=min(f[v][1],min(f[v][2],f[v][3]));
sum+=min(f[v][1],f[v][2]);
}
}
f[u][3]=sum,f[u][2]=INF;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fa){
f[u][2]=min(f[u][2],sum-min(f[v][1],f[v][2])+f[v][1]);
}
}
// printf("f[%d][1]=%d f[%d][2]=%d f[%d][3]=%d\n",u,f[u][1],u,f[u][2],u,f[u][3]);
return;
}
inline void add(int from,int to){
e[++cnt].nxt=head[from];
e[cnt].to=to;
head[from]=cnt;
}
int main(){
int u,v,m;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&u);
scanf("%d%d",&arr[u],&m);
for(int j=1;j<=m;++j){
scanf("%d",&v);
add(u,v);
add(v,u);
}
}//arr記錄價格
//f[i][1]->本節點占用時子樹最小花費
//f[i][2]->本節點不占用但被控制時子樹最小花費
//f[i][3]->本節點完全不受控制時子樹最小花費
dfs(1,0);
// for(int i=1;i<=n;++i){
// printf("size %d = %d\n",i,size[i]);
// }
// size保存子樹大小
printf("%d\n",min(f[1][1],f[1][2]));
return 0;
}
洛谷P2458[SDOI2006]保安站崗 題解 樹上DP