1. 程式人生 > >洛谷P2458[SDOI2006]保安站崗 題解 樹上DP

洛谷P2458[SDOI2006]保安站崗 題解 樹上DP

return 重復 獨立 arr 常用 圖片 保存 根據 準備

題目描述

五一來臨,某地下超市為了便於疏通和指揮密集的人員和車輛,以免造成超市內的混亂和擁擠,準備臨時從外單位調用部分保安來維持交通秩序。

已知整個地下超市的所有通道呈一棵樹的形狀;某些通道之間可以互相望見。總經理要求所有通道的每個端點(樹的頂點)都要有人全天候看守,在不同的通道端點安排保安所需的費用不同。

一個保安一旦站在某個通道的其中一個端點,那麽他除了能看守住他所站的那個端點,也能看到這個通道的另一個端點,所以一個保安可能同時能看守住多個端點(樹的結點),因此沒有必要在每個通道的端點都安排保安。

編程任務:

請你幫助超市經理策劃安排,在能看守全部通道端點的前提下,使得花費的經費最少。

輸入輸出格式

輸入:

\(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