[Luogu][P2458] [SDOI2006]保安站崗

題目連接
看起來彷佛跟最小點覆蓋有點像。但區別在於:
最小點覆蓋要求全部邊在其中,而本題要求全部點在其中。html

即:一個點不選時,它的兒子不必定須要全選。
畫圖理解:node

Example
對於這樣一幅圖,本題中能夠這樣選擇: 3 , 5 3,5
3 3 將相鄰的點 2 , 4 2,4 覆蓋,而 5 5 將相鄰的點 1 , 6 1,6 覆蓋,所以全部點都被覆蓋了。
那麼就必須修改狀態了。web

Dynamic Programing

考慮對於一個點,若是它被覆蓋了,只有三種可能:app

  1. 本身被標記
  2. 父親被標記
  3. 兒子被標記

所以咱們設計狀態:svg

  1. d p [ n o w ] [ 0 ] dp[now][0] 爲本身被標記
  2. d p [ n o w ] [ 1 ] dp[now][1] 爲父親被標記
  3. d p [ n o w ] [ 2 ] dp[now][2] 爲兒子被標記

那麼如何轉移呢?spa

本身被標記時,兒子的3種狀態都是合法的,所以有:
d p [ n o w ] [ 0 ] = min ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 1 ] , d p [ s o n ] [ 2 ] ) dp[now][0] = \sum \min(dp[son][0],dp[son][1],dp[son][2]) 設計

而父親被標記時,兒子不能被父親標記了,只能本身標記或者被兒子的兒子標記。
d p [ n o w ] [ 1 ] = min ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 2 ] ) dp[now][1] = \sum \min(dp[son][0],dp[son][2]) code

被兒子標記的狀況就複雜一些。首先,被兒子標記時,全部兒子確定沒法被父親標記,所以首先有:
d p [ n o w ] [ 2 ] = min ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 2 ] ) dp[now][2] = \sum \min(dp[son][0],dp[son][2]) orm

可是至少須要有一個兒子標記本身,才能讓當前節點被兒子控制。若是一遍下來都沒有選取 d p [ s o n ] [ 0 ] dp[son][0] 的狀況怎麼辦呢?xml

咱們考慮一個兒子,若是它被兒子標記更優,咱們卻強制它標記本身,那麼代價就是 d p [ s o n ] [ 0 ] d p [ s o n ] [ 2 ] dp[son][0] - dp[son][2]

那麼咱們在遍歷兒子時記錄,看是否有兒子選取了控制本身,若是沒有的話,就選擇代價最小的那個兒子強制標記它便可。

Code

實現彷佛沒有太多坑點。
但要注意:當一個點是葉子結點時, d p [ n o w ] [ 2 ] dp[now][2] 即被兒子標記的代價必定是 + +\infty ,在代碼中我直接並在了最後的處理中。能夠感覺一下。

另外就是這題的數據問題,雖說題面是嚴格父親對應兒子,但我建單向邊死活過不了第三個點,改爲雙向邊後AC。

最後答案,根節點不能被父親控制。

#include <cstdio>
#include <cstring>
using namespace std;
template<typename T>
void read(T &r)
{
    static char c; r=0;
    for(c=getchar();c>'9'||c<'0';c=getchar());
    for(;c>='0'&&c<='9';r=(r<<1)+(r<<3)+(c^48),c=getchar());
}
struct node
{
    int to, next;
    node() {}
    node(const int &_to, const int &_next) : to(_to), next(_next) {}
} lines[3002];
int head[1501];
void add(const int &x, const int &y)
{
    static int tot = 0;
    lines[++tot] = node(y, head[x]), head[x] = tot;
}
template<typename T> inline T min(const T &a,const T &b){return a<b?a:b;}
template<typename T> inline T min(const T &a,const T &b,const T &c){return min(min(a,b),c);}
template<typename T> inline T max(const T &a,const T &b){return a>b?a:b;}
int n;
int w[1501];
int dp[1501][3];
//dp[i][0] 本身控制
//dp[i][1] 父親控制
//dp[i][2] 兒子控制
void dfs(int now,int fa)
{
    int v,minp = 999999999;
    bool flag = true;//標記是否強制有一個兒子控制本身
    dp[now][0] = w[now];
    for(int p = head[now];p;p=lines[p].next)
    {
        v = lines[p].to;
        if(v == fa)
            continue;
        dfs(v,now);
        dp[now][0] += min(dp[v][2],dp[v][1],dp[v][0]);
        dp[now][1] += min(dp[v][0],dp[v][2]);
        if(dp[v][0] <= dp[v][2])
        {
            //此時能夠直接選擇這個兒子控制本身了
            flag = false;
            dp[now][2] += dp[v][0];
        }
        else
        {
            dp[now][2] += dp[v][2];
            minp = min(minp,dp[v][0] - dp[v][2]);
        }
    }
    if(flag)
        dp[now][2] += minp;
}
int main()
{
    read(n);
    int m,u,x;
    for(int i = 1;i<=n;++i)
    {
        read(u);
        read(w[u]);
        read(m);
        while(m--)
        {
            read(x);
            add(u,x);
            add(x,u);//竟然要雙向邊?!
        }
    }
    dfs(1,0);
    printf("%d",min(dp[1][0],dp[1][2]));
    return 0;
}
相關文章
相關標籤/搜索