皇宫看守


Description:

太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。 
皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。 
可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。 

Input:

帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

Output:

输入文件中数据表示一棵树,描述如下: 
第1行 n,表示树中结点的数目。 
第2行至第n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i(0<i<=n),在该宫殿安置侍卫所需的经费k,该边的儿子数m,接下来m个数,分别是这个节点的m个儿子的标号r1,r2,...,rm。
对于一个n(0 < n<=1500)个结点的树,结点标号在1到n之间,且标号不重复。 


这道题中的数据是一棵树,很容易想到树形DP。//树的最小带权覆盖集

乍一看此题和战略游戏十分接近(所以我一开始就按战略游戏的代码写),但是往深处想他们之间还是有不同的。这道题要求最小花费,而战略游戏求解最少安放士兵的数目,所以在转移上有一点(很大)不同。

solution:

此题没有明确的说明根节点,所以在读入时要做一下处理,找到没有子结点的点当成根结点(下文中用root代替),方便进行树上递归。

转移:

前面也说过,树上的DP转移一般有两个方向:从根到叶和从叶到根。此题满足从叶到根。

对于每一个结点,只要在其本身或者父结点或者子结点有侍卫时,就一定可以被观察到,所以转移的状态可以简单的分为这三种。(son[x]表示x的子结点)

  f[x][0]表示x没有侍卫,但x的父结点有侍卫;

  f[x][1]表示x没有侍卫,但x的子结点有侍卫;

  f[x][2]表示x本身有侍卫。

对于f[x][0]的情况:

  此时x结点已经被观察到,因为想要保证花费最小,所以此时x结点不必要安放侍卫即可。

  那么对于x的所有子结点只有两种情况:自己有侍卫或者可以被后代有侍卫。

  状态转移方程是f[x][0]+=min(f[son[x]][2],f[son][x]][1])

  (先不要纠结d是什么意思,等下我会解释) 

对于f[x][1]的情况:

  此时x结点已经被观察到,所以x结点不必安放侍卫。

  只用找出x的所有子结点中花费最小的即可。

  同样的,子结点也只有上述的两种情况。

  状态转移方程是f[x][1]+=min(f[son[x]][2],f[son[x]][1])+d

  这个充满神秘感的d又一次出现了,现在我要揭开它神秘的面纱:

  d=min(d,f[son[x]][2]-min(f[son[x]][2],f[son[x]][1]))

  因为刚才所说的都是对结点x进行决策的,对于f[x][0]f[x][1],如果最优解是从f[son][x]][1]转移来的,那么必然也要加上它的花费。

  所以d的作用就是判断是否是从f[son][x]][1]转移来,如果是就加上它的花费。(具体使用参考以下程序)

对于f[x][2]的情况:

  此时x结点已经有侍卫了,x的儿子可以有侍卫,也可以被其子结点看守,还可以被其父结点看守。

  所以状态转移方程是min(f[son[x]][0],f[son[x]][1],f[son[x]][2])+w[x]

  (w[x]表示在x结点安放侍卫的花费)

出口:

最后对f[root][1]f[root][2]取最小就是最终答案

具体转移就是这样,下面放代码咯:)

#include<bits/stdc++.h>
using namespace std;
vector<int> s[1505];
int w[1505];
int f[1505][3];
bool v[1505];
void dp(int u){
//    f[u][2]//self
//    f[u][1]//son
//    f[u][0]//fa
    int y;
    int d=0x7fffffff/2;
    int sz=s[u].size();
    for(int i=0;i<sz;i++){
        dp(s[u][i]);
        f[u][0]+=min(f[s[u][i]][2],f[s[u][i]][1]);
        f[u][1]+=min(f[s[u][i]][2],f[s[u][i]][1]);
        d=min(d,f[s[u][i]][2]-min(f[s[u][i]][2],f[s[u][i]][1]));
        f[u][2]+=min(f[s[u][i]][2],min(f[s[u][i]][1],f[s[u][i]][0]));
    }
    f[u][1]+=d;
    f[u][2]+=w[u];
}
int main(){
    int n;
    cin>>n;
    int num,k,r,m;
    for(int i=1;i<=n;i++){
        cin>>num>>k>>m;
        w[num]=k;
        for(int j=1;j<=m;j++){
            cin>>r;
            v[r]=1;
            s[num].push_back(r);
        }
    }
    int root;
    for(int i=1;i<=n;i++){
        if(v[i]==0){
            root=i;
            break;
        }
    }
    dp(root);
    cout<<min(f[root][1],f[root][2]);
    return 0;
} 
皇宫看守

 代码中我用的是vector来实现链表,当然也可以用结构体来实现。

就这么多啦:)欢迎指正!!

原文地址:https://www.cnblogs.com/duojiaming/p/11220769.html