P1272 重建道路

解题思路

这道题要求我们找到最少需要切断多少条道路,才能分离出一个恰好包含 P 个节点的子树。这是一个典型的树形动态规划问题。

关键思路:

  1. 树形结构处理:题目给出的是一棵树,我们需要递归处理每个子树。

  2. 动态规划状态定义:dp[i][j]表示以节点i为根的子树中,分离出j个节点需要切断的最少道路数。

  3. 状态转移:对于每个节点,我们考虑它的每个子节点,通过组合不同子树的节点数来更新dp值。

  4. 初始化:dp[i][1] = 0,因为不需要切断任何道路就能保留节点i自身。

  5. 结果计算:最终答案是所有节点中dp[x][p]的最小值,对于非根节点需要+1(因为要切断它与父节点的连接)。

注意事项:

  • 需要处理根节点和非根节点的区别(根节点不需要切断与父节点的连接)

  • 在状态转移时要注意遍历顺序(倒序)以避免重复计算

代码注释

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
vector<int> g[N];  // 邻接表存储树结构
int n,p;           // n:节点总数, p:需要分离的子树大小
int a[N],f[N],dp[N][N]; 
// a[i]:以i为根的子树节点数
// f[i]:节点i的父节点
// dp[i][j]:以i为根的子树中分离出j个节点需要的最小切断数
int ans = 1e9;     // 存储最终答案,初始化为一个大数

void dfs(int x,int fa) {
    f[x] = fa;      // 记录父节点
    a[x] = 1;       // 初始化子树大小为1(包含自己)
    
    // 遍历所有子节点
    for(int i = 0; i < g[x].size(); i++) {
        int y = g[x][i];
        if(y == fa) continue;  // 跳过父节点
        
        dfs(y,x);    // 递归处理子节点
        a[x] += a[y]; // 更新子树大小
        
        // 动态规划处理,倒序更新避免重复计算
        for(int j = a[x]; j >= 1; j--) {
            dp[x][j] += 1; // 初始情况:直接切断与子节点y的连接,代价+1
            
            // 尝试不切断与y的连接,而是从y的子树中取k个节点
            for(int k = 1; k <= min(j - 1,a[y]); k++)
                dp[x][j] = min(dp[x][j], dp[x][j - k] + dp[y][k]);
        }
    } 
    
    // 更新全局答案,如果x不是根节点(1),需要+1(切断与父节点的连接)
    ans = min(ans, dp[x][p] + (x != 1));
}

int main() {
    cin >> n >> p;
    // 读入树结构
    for(int i = 1; i < n; i++) {
        int x,y; cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    
    memset(dp,0x3f,sizeof(dp)); // 初始化DP数组为极大值
    for(int i = 1; i <= n; i++)
        dp[i][1] = 0;  // 分离出1个节点(自己)不需要切断任何边
    
    dfs(1,0);  // 从根节点1开始DFS遍历
    
    cout << ans;
    return 0;
}

 

posted @ 2025-06-11 21:18  CRt0729  阅读(19)  评论(0)    收藏  举报