1. 程式人生 > >[SDOI2006]保安站崗 洛谷p2458

[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;
 }