题目链接:http://acm.ocrosoft.com/problem.php?cid=1630&pid=16

题目描述:

太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。

    皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;有边直接相连的宫殿可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。 

    可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

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

输入

    输入文件中数据表示一棵树,描述如下: 

    1 n,表示树中结点的数目。 

    2行至第n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i0<I<=N),在该宫殿安置侍卫所需的经费K,该点的儿子数M,接下来M个数,分别是这个节点的M个儿子的标号R1R2...RM

    对于一个n0 < n<=1500)个结点的树,结点标号在1n之间,且标号不重复。 

输出

输出文件仅包含一个数,为所求的最少的经费。 

样例输入

6

1 30 3 2 3 4

2 16 2 5 6

3 5 0

4 4 0

5 11 0

6 5 0

样例输出

25

思路:

一个树形dp问题,分别用dp[i][0]表示i点放看守,dp[i][1]表示i点不放看守i点被儿子监视,dp[i][2]表示i点不放看守i点被父亲节点监视。

(1) dp[i][0] = 所有子节点t的dp[t][0], dp[t][1], dp[t][2]中最小的一个的和 + vi[i] (min(dp[t][0], min(dp[t][1], dp[t][2]))+vi[i])

(2) dp[i][1] = 某个子节点放看守 + 其他节点的dp[t][0], dp[t][1]中最小的一个的和

(3) dp[i][2] = 所有子节点的dp[t][1]的和

 

代码:

#include<bits/stdc++.h>

using namespace std;

#define ll long long

#define maxn 1505

#define inf 0x3f3f3f3f

int n, v[maxn], son_num[maxn],son[maxn][maxn],vis[maxn];

ll dp[maxn][3], tmp[maxn];//dp[i][0] i点放看守,dp[i][1] i点不放看守i点被儿子监视,dp[i][2] i点不放看守i点被父节点监视

void tree(int x)

{

         if (dp[x][0])return;

         for (int i = 1; i <= son_num[x]; i++)

         {

                  int t = son[x][i];//记录子节点的序号

                  tree(t);//树形dp

                  dp[x][0] += min(dp[t][0], min(dp[t][1], dp[t][2]));//如果再x位置上放了守卫,那随便加子节点的最小值就行

                  dp[x][2] += dp[t][1]; //如果x位置是被父节点保护的,且x不放首位,为了x的子节点被保护,那x只能有x的子节点的子节点保护

         }

         dp[x][0] += v[x];//加上自己位置放上守卫的价格

         memset(tmp, 0, sizeof(tmp));

         ll tt = 0;//如果x位置是由子节点保护的

         for (int i = 1; i <= son_num[x]; i++)

         {

                  int t = son[x][i];

                  tmp[i] = min(dp[t][0], dp[t][1]);//并不需要每个子节点都有守卫

                  tt += tmp[i];

         }

         dp[x][1] = inf;//初始化无穷大

         for (int i = 1; i <= son_num[x]; i++)

         {

                  int t = son[x][i];

                  if (tt - tmp[i] + dp[t][0] < dp[x][1])//保证x位置的子节点至少有一个守卫

                          dp[x][1] = tt - tmp[i] + dp[t][0];

         }





}

int main()

{

         int root;//根

         cin >> n;

         for (int i = 1; i <= n; i++)

         {

                  int x;

                  cin >> x;

                  cin >> v[x] >> son_num[x];//录入每个节点放守卫的价值和每个节点有几个子节点

                  for (int j = 1; j <= son_num[x]; j++)

                  {

                          cin >> son[x][j];

                          vis[son[x][j]] = 1;//所有的子节点只要存在记录为1

                  }

         }

         for (int i = 1; i <= n; i++)

         {

                  if (!vis[i])//不为1的第一个节点作为根

                  {

                          root = i;

                          break;

                  }      

         }

         tree(root);//树形dp

         cout << min(dp[root][0],dp[root][1]) << endl;

}