洛谷 P1272 重建道路 解题报告

本文介绍了一个关于树形DP的经典问题——P1272重建道路的解题思路与代码实现。该问题关注如何计算最少切断多少条边可以使包含特定数量节点的子树与其他部分分离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P1272 重建道路

题目描述

一场可怕的地震后,人们用\(N\)个牲口棚\((1≤N≤150\),编号\(1..N\))重建了农夫\(John\)的牧场。由于人们没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是惟一的。因此,牧场运输系统可以被构建成一棵树。\(John\)想要知道另一次地震会造成多严重的破坏。有些道路一旦被毁坏,就会使一棵含有\(P(1≤P≤N)\)个牲口棚的子树和剩余的牲口棚分离,\(John\)想知道这些道路的最小数目。

输入输出格式

输入格式:

第1行:2个整数,\(N\)\(P\)

\(2..N\)行:每行2个整数\(I\)\(J\),表示节点\(I\)是节点\(J\)的父节点。

输出格式:

单独一行,包含一旦被破坏将分离出恰含\(P\)个节点的子树的道路的最小数目。


最近很颓,这个还算裸的树形DP也卡了我好长时间。


首先明确一点(卡了我好久),这是一颗树,根是可以随便选的。

如果只按照题目把树输入是会大的(然而数据水只错了两个点)


\(dp[i][j]\)代表根节点为\(i\)的子树在失去(注意是失去)\(j\)个点时 切掉的边的最小个数。

\(dp[i][j]=min(dp[i][j],dp[i][j-k]+dp[i_{son}][k])\)

  • 注意:这个已经滚动掉了一维(第几个子树) 枚举\(j\)时要倒着哦

有个问题,对于直接切掉自己儿子的情况怎么办?

最开始时我这样做了,在每次枚举\(j\)的时候

\(dp[i][j]=min(dp[i][j],dp[i][j-size[i_{son}]]+1);\)

然而这样做是重复了的。(想想看)

有个很喵(妙)的做法。

\(dfs\)每次求完\(i\)时把她自己切掉就好了啦 -> \(dp[i][size[i]]=1\);

回到最开始,虽然根不确定,其实只需要随便取一种根的情况就行拉。

比如说\(root\)是根,\(dp[root][n-p]\)肯定不包含切她自己的情况

我们就在其他节点中,把自己切成一棵树,即用\(dp[i][p-(n-size[i])]+1\)更新\(ans\)

#include <cstdio>
#include <cstring>
int min(int x,int y) {return x<y?x:y;}
const int N=156;
int dp[N][N];//子树i舍弃j个点的最小切割数
int n,p,root;
int g[N][N],used[N],size[N];

void dfs(int now)
{
    dp[now][0]=0;
    for(int i=1;i<=n;i++)//枚举子树
        if(g[now][i])
        {
            dfs(i);
            size[now]+=size[i];
            int r=min(p,size[now]);
            for(int j=r;j>=1;j--)//枚举当前舍弃节点数
            {
                int r2=min(min(p,size[i]),j);
                for(int k=1;k<=r2;k++)//枚举子树状态
                    dp[now][j]=min(dp[now][j],dp[now][j-k]+dp[i][k]);
            }
        }
    dp[now][++size[now]]=1;
}

int main()
{
    memset(dp,0x3f,sizeof(dp));
    scanf("%d%d",&n,&p);
    int u,v;
    p=n-p;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        g[u][v]=1;
        used[v]=1;
    }
    for(int i=1;i<=n;i++)
        if(!used[i])
        {
            root=i;
            break;
        }
    dfs(root);
    int ans=0x3f3f3f3f;
    for(int i=1;i<=n;i++)
    {
        if(i==root) {ans=min(ans,dp[i][p]);continue;}
        int tt=p+size[i]-n;//还要砍得
        if(tt>=0) ans=min(ans,dp[i][tt]+1);
    }
    printf("%d\n",ans);
    return 0;
}

2018.5.6

转载于:https://www.cnblogs.com/butterflydew/p/8998703.html

### 洛谷 P1414 C++ 解题思路 洛谷 P1414 是一道涉及字符串处理与简单逻辑判断的题目。以下是关于该问题的专业解题思路。 #### 题目解析 洛谷 P1414 的核心任务是根据输入的一系列字符,按照特定规则进行计算并输出结果。此题的关键在于如何高效地处理输入数据,并正确实现题目所要求的逻辑运算。 #### 数据结构与算法设计 为解决上述问题,可以采用以下方法: - **字符串遍历**:通过逐个字符遍历输入字符串,识别其中的数字和运算符。 - **栈的应用**:利用栈这一数据结构来存储中间计算结果,确保在遇到运算符时能够正确取出最近的两个操作数进行计算[^1]。 - **状态维护**:在遍历过程中,维护当前的状态(如当前数字、是否需要进行运算等),以确保逻辑的正确性。 以下是基于上述设计的 C++ 实现代码: ```cpp #include <iostream> #include <stack> #include <string> using namespace std; int main() { string expr; cin >> expr; stack<int> nums; int num = 0; for (char c : expr) { if (c >= '0' && c <= '9') { num = num * 10 + (c - '0'); } else if (c == '+' || c == '-' || c == '*' || c == '/') { nums.push(num); num = 0; int b = nums.top(); nums.pop(); int a = nums.top(); nums.pop(); if (c == '+') nums.push(a + b); else if (c == '-') nums.push(a - b); else if (c == '*') nums.push(a * b); else if (c == '/') nums.push(a / b); } } nums.push(num); cout << nums.top() << endl; return 0; } ``` #### 注意事项 - 在处理输入字符串时,需特别注意数字可能超过一位的情况,应正确处理多位数字的拼接。 - 运算符优先级在此题中无需特别考虑,因为题目保证了表达式的合法性。 - 需要对除零的情况进行特殊处理,以避免运行时错误。 #### 测试与验证 为确保算法的正确性,可以通过以下测试用例进行验证: - 测试用例 1:输入 `3+5*2`,输出 `13`。 - 测试用例 2:输入 `10-3/3`,输出 `9`。 - 测试用例 3:输入 `7*8-4`,输出 `52`。 ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值