POJ 1947 Rebuilding Roads(树形DP)

Rebuilding Roads

# 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;
}

猜你喜欢

转载自blog.csdn.net/ccshijtgc/article/details/81059417