传送门 |
---|
http://poj.org/problem?id=1463 |
这是一道树形DP入门题。
分析符号说明
- node_num:结点数
- edge_num:边数
- saved_edge_num:无向图中存储的边的数量
- dp[][]:动态规划数组dp[i][j]表示第i的结点是(j=1)否(j=0)有兵的情况下其与子结点的所需兵的最小值
- x:父结点
- y:子结点
分析
- 由题目中
the roads of which form a tree
知道路是呈树形的【这一点很重要,如果不是树,下面的AC代码会导致无限DFS爆栈,文末我会给出一个简单的例子说明】任意一个结点作为根结点都可以。 - 我存的是无向图,由树,知
edge_num = node_num - 1
因此在存道路的时候存边的次数满足
0 < saved_edge_num ≤ 2 * ( 1500 - 1 )
- 状态转移方程
- 父结点有兵,那么子结点可以有兵也可以没有兵,所以有
dp[x][1] = Σ min{ dp[y][0], dp[y][1] }
- 父结点无兵,那么子结点必须有兵,所以有
dp[x][0] = Σ dp[y][1]
- 父结点有兵,那么子结点可以有兵也可以没有兵,所以有
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1505;
int node_num;//结点数
int nodeid, current_node_num, anothernode;//当前结点id,当前结点所连结点树,某一其他结点
int t[N*2], ne[N*2], h[N], idx;//存图
int dp[N][2];//dp数组
inline int smaller(const int &a,const int &b) {
return a < b ? a : b;
}
inline void addEdge(int be,int en) {
t[idx] = en;
ne[idx] = h[be];
h[be] = idx++;
}
void dfs_dp(int x,int fa) {//树形DP
dp[x][1] = 1;
dp[x][0] = 0;
for (int i = h[x]; i != -1; i = ne[i]) {
if (t[i] == fa)
continue;
dfs_dp(t[i], x);
dp[x][1] += smaller(dp[t[i]][0], dp[t[i]][1]);
dp[x][0] += dp[t[i]][1];
}
}
int main() {
while (scanf("%d", &node_num) != EOF) {
memset(h, -1, sizeof(h));
idx = 0;
for (int i = 0; i < node_num; ++i) {
scanf("%d:(%d)", &nodeid, ¤t_node_num);
for (int j = 0; j < current_node_num; ++j) {
scanf("%d", &anothernode);
addEdge(nodeid, anothernode);
addEdge(anothernode, nodeid);
}
}
dfs_dp(0, -1);//0作为根结点
printf("%d\n", smaller(dp[0][1], dp[0][0]));
}
return 0;
}
ps:如果是图
以下为一个图(非树)的道路的输入例子:
3
0:(2) 1 2
1:(1) 2
2:(0)
在这个情况下DFS递归的时候就会出现问题了,上图中会按照顺时针永远DFS递归运行,无法正常结束,直至出现爆栈异常被终止。