树形dp 状态机dp

题目描述

#10157. 「一本通 5.2 例 5」皇宫看守 - 题目 - LibreOJ (loj.ac) 太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。

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

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

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

输入格式

输入中数据描述一棵树,描述如下:

第一行 ,表示树中结点的数目。

第二行至第  行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号 ,在该宫殿安置侍卫所需的经费 ,该边的儿子数 ,接下来  个数,分别是这个节点的  个儿子的标号 

对于一个  个结点的树,结点标号在  到  之间,且标号不重复。

输出格式

输出最少的经费

样例

input

6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0

output

25

有六个区域被安排的情况如左图所示。

如右图,灰色点安排了警卫, 号警卫可以观察  号警卫可以观察  号警卫可以观察 

总费用:

数据范围与提示

对于  的数据,

思路

条件和战略游戏很像, 但上一题是求选择的个数, 这里不但要满足条件还要找到最小的摆放位置, 也就是说光知道该点是否被覆盖不行, 得细分为被哪个节点覆盖。

单是两个状态表示当前节点是否放棋子已经无法满足要求, 需要扩增。

状态表示为: f[i][0] 可以被父节点看到, 且当前点无棋子 f[i][1] 可以被子节点看到, 且当前点无棋子 f[i][2] 当前放棋子

状态转移: f[i][0] = min(f[j][1], f[j][2]) 此时i上无棋子, 子节点可以被其子节点看到或者放棋子 f[i][2] = min(f[j][0], f[j][1], f[j][2]) 此时i上有棋子, 子节点随意 f[i][1] = min(f[j][2] + min(f[k][1], f[k][2])) k为除j之外的子节点 需要确定从某一子节点转移过来, 其他的子节点只可能被其子节点看到或者自己放棋子 因为f[i][0]本身就代表了i子节点的最小值之和 故求f[i][1]所需要的min(f[k][1], f[k][2])时, 可以用所有子节点之和减去当前子节点, 这样就能得出来除j之外的子节点之和。 即f[i][1] = min(f[i][1], f[j][2] + f[u][0] - min(f[j][1], f[j][2]))

代码

 
        int j = e[i];
        f[u][1] = min(f[u][1], f[j][2] + f[u][0] - min(f[j][1], f[j][2]));
    }
}
 
int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++)
    {
        int a, m;
        scanf("%d", &a);
        scanf("%d%d", &v[a], &m);
        for (int i = 1; i <= m; i++)
        {
            int b;
            scanf("%d", &b);
            add(a, b);
            st[b] = true;
        }
    }
    int root = 1;
    while (st[root])
        root++;
    // cout << root << endl;
    dfs(root);
    printf("%d\n", min(f[root][1], f[root][2]));
    return 0;
}