# include<iostream>
# include<cstdio>
# include<cstring>
# include<vector>
# include<algorithm>
using namespace std;
const int N = 155;
const int INF = 0x3f3f3f3f;
int n, m;
bool flag[N];
int dp[N][N];
vector<int>e[N];
void init() {
int a, b;
for(int i = 1; i <= n; ++i) {
e[i].clear();
for(int j = 0; j <= m; ++j)
dp[i][j] = INF;
}
memset(flag, false, sizeof(flag));
while(scanf("%d%d", &a, &b) == 2) {
e[a].push_back(b);
flag[b]=true;//是否是儿子
}
}
//题目分析:定义状态dp(root,k)表示在以root为根节点的子树中,删掉一些边变成恰有k个节点的新树需要删去的最少边数。对于根节点root的某个儿子son,要么将son及其所有的子节点全部删掉,则dp(root,k)=dp(root,k)+1,只需删除root与son之间的边;要么在son的子树中选出一些边删掉,构造出有j个节点的子树,状态转移方程为dp(root,k)=max(dp(root,k),dp(son,j)+dp(root,k-j))。
void dfs(int u) {
dp[u][1] = 0;//首先我们来考虑边界,如果是叶子,则dp[rt][1]=0是显而易见的
for(int i = 0; i < e[u].size(); ++i) {
int v = e[u][i];
dfs(v);
for(int j = m; j >= 1; --j) {//这里j需要从大往小和01背包类似
dp[u][j] += 1;//要么将son及其所有的子节点全部删掉,则dp(root,k)=dp(root,k)+1
for(int k = 1; k < j; ++k) { ///k从1循环到j-1,一定不能从0循环到j
dp[u][j] = min(dp[u][j], dp[v][k]+dp[u][j-k]);
}
}
}
}
void solve() {
int ans = INF;
for(int i = 1; i <= n; ++i) {
if(flag[i])//从根节点开始撸
continue;
dfs(i);
//break;
}
for(int i = 1; i <= n; ++i) {
int tmp;
if(!flag[i])//根节点
tmp = dp[i][m];
if(flag[i]) //取dp[i][m]为答案时,如果i不是根节点, i则要脱离它的父亲节点,边数要加1
tmp = dp[i][m]+1;
ans = min(ans, tmp);
}
printf("%d\n", ans);
}
int main() {
while(~scanf("%d%d", &n, &m)) {
init();
solve();
}
return 0;
}
POJ 1947 Rebuilding Roads(树形DP)
猜你喜欢
转载自blog.csdn.net/ccshijtgc/article/details/81059417
今日推荐
周排行