AcWing 1077. 皇宫看守
题目传送门
一、算法分析
状态表示
\(f[i,0]\):在点 \(i\) 不摆放警卫,且被父结点看到的所有摆放方案的最小花费
\(f[i,1]\):在点 \(i\) 不摆放警卫,且被子结点看到的所有摆放方案的最小花费
\(f[i,2]\):在点 \(i\) 摆放警卫的所有方案的最小花费
状态转移
\(f[i,0]=∑min(f[j,1],f[j,2])\)
\(f[i,2]=∑min(f[j,0],f[j,1],f[j,2])\)
\(f[i,1]=min(f[k,2]+\sum\limits_{j≠k}min(f[j,1],f[j,2]) )\)
注意:
计算\(f[i,1]\)时,先把所有儿子的\(min(f[j,1],f[j,2])\)的总和 \(sum\) 计算出来,当使用第 \(k\) 个儿子放警卫时,把第 \(k\) 个儿子的 \(min(f[k,1],f[k,2])\) 减去,便得到了 $\sum\limits_{j≠k}min(f[j,1],f[j,2]) $
二、实现代码
#include
using namespace std;
const int INF = 0x3f3f3f3f;
/*
以下注释为早期笔记,希望对你有所帮助
状态机 + 树形Dp问题
状态表示:
f(i, 0):第i号结点被他的父结点安排的守卫看住的方案数
f(i, 1):第i号结点被他的子结点安排的守卫看住的方案数
f(i, 2):第i号结点自己安排守卫看住的方案数
状态计算:(j是i的子结点)
f(i, 0) = sum{min(f(j,1), f(j,2))}
i是被他父结点看住的,那他的子结点要么自己看自己,要么被自己的子结点看住
f(i, 1) = min{w(k) + f(k, 2) + sum{min(f(j,1), f(j,2))}}
i如果是被子结点看住的,那么枚举被子结点看住的所有方案,对所有方案求最小值
这里的sum不包括j==k的情况,因此需要手动额外减去
f(i, 2) = sum{min(f(j,0), f(j,1), f(j,2))} + w(u)
i是被自己看住的,那他的子结点可以被父结点看住,可以自己看自己,也可以被自己的子结点看住
*/
const int N = 1510;
int n;
int h[N], w[N], e[N], ne[N], idx;
int f[N][3];
bool st[N];
//邻接表
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u) {
f[u][0] = 0;
f[u][1] = INF; //f[u][1]求最小值,初始化为最大值
f[u][2] = w[u];//初始化放置自己的代价
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
dfs(j);
//u是被父结点看住,那么子结点j只能靠自己或靠自己的孩子
f[u][0] += min(f[j][1], f[j][2]);
//u自己看住自己,那么子结点j可以靠父节点,自己,子节点
f[u][2] += min(min(f[j][0], f[j][1]), f[j][2]);
}
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
//f[u][0]中记录了sum{min(f(j,1), f(j,2))},再从中减去对应的贡献即可得到remain ver
f[u][1] = min(f[u][1], f[u][0] + f[j][2] - min(f[j][1], f[j][2]));
}
}
int main() {
//初始化邻接表
memset(h, -1, sizeof h);
cin >> n;
//读入n个结点
for (int i = 1; i <= n; i++) {
int x, m;
//结点号,费用,连接的点个数
cin >> x >> w[x] >> m;
while (m--) {
int y;
cin >> y;
//建立有向图
add(x, y);
//标识不是根
st[y] = true;
}
}
//找到根
int root = 1;
while (st[root]) root++;
//从根开始进行有向图的树形DP
dfs(root);
printf("%d\n", min(f[root][1], f[root][2]));
return 0;
}