[SDOI2006]保安站崗 洛谷p2458
題目描述
五一來臨,某地下超市為了便於疏通和指揮密集的人員和車輛,以免造成超市內的混亂和擁擠,準備臨時從外單位呼叫部分保安來維持交通秩序。
已知整個地下超市的所有通道呈一棵樹的形狀;某些通道之間可以互相望見。總經理要求所有通道的每個端點(樹的頂點)都要有人全天候看守,在不同的通道端點安排保安所需的費用不同。
一個保安一旦站在某個通道的其中一個端點,那麼他除了能看守住他所站的那個端點,也能看到這個通道的另一個端點,所以一個保安可能同時能看守住多個端點(樹的結點),因此沒有必要在每個通道的端點都安排保安。
程式設計任務:
請你幫助超市經理策劃安排,在能看守全部通道端點的前提下,使得花費的經費最少。
輸入輸出格式
輸入格式:
第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
(以下全部都是對於要覆蓋任意一個以x為根的子樹來說的)
(其中我們設y節點為y的兒子,fa為x的父親)
1.x節點被自己覆蓋,即選擇x點來覆蓋x點
2.x節點被兒子y覆蓋,即選擇y點來覆蓋x點
3.x節點被父親fa覆蓋,即選擇fa點來覆蓋x點
藉此三種狀態,我們可以設f[x][0/1/2]為讓以x為根的子樹中的節點全部被覆蓋,且x點的被覆蓋情況為1/2/3時的最小代價
為了方便,我們不妨設這三種情況分別為:
1.f[x][0]---對應上面的1
2.f[x][1]---對應上面的2
3.f[x][2]---對應上面的3
既然是DP,總是有轉移方程的,我們想一下dp方程要如何設計
設計狀態轉移方程:
(1):對應上面的1.
f[x][0]=∑ min(f[y][0],f[y][1],f[y][2]) + val[x]
其中val[x]是選擇x點的代價
我們很容易想到,在節點x被選擇之後,我們就可以無拘無束了(蛤?),也就是說對於x兒子節點y的狀態可以不去考慮,因為x節點被選擇之後y節點無論如何也會被覆蓋到,所以我們在兒子y的所有狀態裡取min,累加起來就行了
(2):對應上面的3(先講3,因為2比較難以理解,放到了後面)
f[x][2]=∑ min(f[y][0],f[y][1])
為什麼3情況對應的轉移方程要這樣寫呢?
我們不妨這樣理解,對於x節點我們讓它的父親節點fa覆蓋它,那麼根據我們的狀態設計,此時必須要滿足以x的兒子y為根的子樹之中所有點已經被覆蓋
那麼這時就轉化為一個子問題,要讓y子樹滿足條件,只有兩種決策:要麼y被y的兒子覆蓋,要麼被y自己覆蓋(即選擇y節點),只需要在y的這兩種狀態取min累加就可以了
(3):對應上面的2(DuangDuangDuang 敲黑板劃重點啦)
f[x][1]=∑ min(f[y][0],f[y][1]),如果選擇的全部都是f[y][1],要再加上min(f[y][0]-f[y][1])
這又是什麼意思呢?真是讓人摸不著頭髮,,,質壁分離(逃
到了這裡,我們就要回顧一下我們設計的dp狀態了:
設f[x][0/1/2]為讓以x為根的子樹中的節點全部被覆蓋,且x點的被覆蓋情況為1/2/3時的最小代價
先提示一下,如果你理解了下面,那麼本題是很簡單的。。如果你沒理解,就返回到這裡再看一遍吧,我就在這裡等著你
咳咳。。說正經的。。(逃
對於此時的狀態,f[x][1]代表對於節點x讓x被自己的兒子覆蓋,那麼和分析(2)一樣,都要先滿足此時以y的子樹已經滿足了條件,才能進行轉移,這就是前面那部分:∑ min(f[y][0],f[y][1])的來歷,那麼後面那一長串又是怎麼回事呢?
我們可以這樣理解,此時既然要保證x點是被自己的兒子覆蓋的,那麼如果此時y子樹已經滿足了全部被覆蓋,但是y此時被覆蓋的狀態卻是通過y節點自己的兒子達到的,那麼x就沒有被兒子y覆蓋到,那麼我們不妨推廣一下,如果x所有的兒子y所做的決策都不是通過選擇y點來滿足條件,那麼我們就必須要選擇x的一個子節點y,其中y滿足f[y][0]-f[y][1]最小,並把這個最小的差值累加到f[x][1]中去,這樣才能使得x點被自己的兒子覆蓋,狀態f[x][1]也才能合理地得到轉移
好了,如果你還是沒有太懂這個(3)的設計過程,請你回到之前再仔細看幾遍
如果你已經理解了上面,那麼恭喜你這個題,你已經A掉了
因為轉移方程既然有了,那麼我們就只需要最後的答案了
由於題目中沒有說這棵樹的根節點是哪個,所以你可以預設1就是根,或者開一個數組在加邊的時候記錄一下每個點的入度,最後沒有入度的點就是根(但好像沒有區別,畢竟我A掉了)
最後答案即為min(f[root][0],f[root][1])
因為根節點沒有父親,所以不需要考慮它的f[root][2]狀態
那這樣的花。。下面就放一下我醜陋的程式碼好了(逃
還請dalao們不喜勿噴
PS:程式碼裡也有解釋,希望能幫到你更深地理解一下本題
#include<bits/stdc++.h>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN=3005,INF=100000000;
struct Edge{
int v,nxt;
}e[MAXN<<1];
int f[3][MAXN<<1],h[MAXN<<1],tot,n;
int a[MAXN<<1];
inline void add(int u,int v)
{
e[tot].v=v;
e[tot].nxt=h[u];
h[u]=tot++;
}
void dfs(int u,int fa)
{
int i,flag=0,min_las=INF;
f[0][u]=a[u];
for(i=h[u];~i;i=e[i].nxt){
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);
f[0][u]+=min(f[0][v],min(f[1][v],f[2][v]));
f[2][u]+=min(f[0][v],f[1][v]);
if(f[0][v]<f[1][v]) flag=1;
else min_las=min(min_las,f[0][v]-f[1][v]);
f[1][u]+=min(f[0][v],f[1][v]);
}
if(!flag) f[1][u]+=min_las;
}
int main()
{
ios::sync_with_stdio(false);
memset(h,-1,sizeof(h));
int i,j,k,u,v;
cin>>n;
f(i,1,n){
cin>>u;
cin>>a[u]>>k;
f(j,1,k){
cin>>v;
add(u,v);
add(v,u);
}
}
dfs(1,-1);
cout<<min(f[0][1],f[1][1])<<endl;
return 0;
}