[洛谷P1272] 重建道路

类型:树形背包

传送门:>Here<

题意:给出一棵树,要求断开$k$条边来分离出一棵有$P$个节点的子树。求最小的$k$

解题思路

和上一题类型相同,但不那么好做了——分离出的一棵子树肯定是在一起的,不能是散的,因此这给dp带来了难度

$dp[u][i][j]$表示节点$u$的子树内,在前$i$棵子树内分离出有$j$个节点的子树,最少断的边。特别需要注意的是,这里的有$j$个节点的子树必须包含节点$u$

想到这个定义以后就不难了,有方程$$dp[u][i][j]=Min\{dp[u][i-1][j-k]+dp[v][numson[v]][k]-2\}$$相当于在前$i-1$棵子树中分离出包含$u$的大小为$j-k$的子树,并且在当前子树分离出$k$的。注意为什么要$-2$,因为这两个部分为了确保独立性,前者肯定会断掉边$(u,v)$,后者也肯定会断掉边$(u,v)$,而现在恰好需要这条边来把两棵子树连起来,因此返还2

一样可以滚动$$dp[u][j]=Min\{dp[u][j-k]+dp[v][k]-2\}$$

初始化:$dp[u][1]=u的入度$ 注意$dp[u][0]$是没有意义的,因此不作处理

Code

/*By DennyQi 2018.8.14*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define  r  read()
#define  Max(a,b)  (((a)>(b)) ? (a) : (b))
#define  Min(a,b)  (((a)<(b)) ? (a) : (b))
using namespace std;
typedef long long ll;
const int MAXN = 160;
const int MAXM = 320;
const int INF = 1061109567;
inline int read(){
    int x = 0; int w = 1; register int c = getchar();
    while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
    if(c == '-') w = -1, c = getchar();
    while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * w;
}
int N,P,ans(INF),x,y,first[MAXM],nxt[MAXM],to[MAXM],cnt,dp[MAXN][MAXN],rd[MAXN];
inline void add(int u, int v){
    to[++cnt]=v,nxt[cnt]=first[u],first[u]=cnt;
    ++rd[u], ++rd[v];
}
inline void DP(int u, int _f){
    int v;
    for(int i = first[u]; i; i = nxt[i]){
        if((v=to[i]) == _f) continue;
        DP(v, u);
        for(int j = P; j; --j)
            for(int k = 1; k <= j; ++k)
                dp[u][j] = Min(dp[u][j], dp[u][j-k] + dp[v][k] - 2);
    }
}
int main(){
    memset(dp,0x3f,sizeof(dp));
    N=r,P=r;
    for(int i = 1; i < N; ++i) x=r,y=r,add(x, y);
    for(int i = 1; i <= N; ++i) dp[i][1] = rd[i];
    DP(1, -1);
    for(int i = 1; i <= N; ++i) ans = Min(ans, dp[i][P]);
    printf("%d", ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/qixingzhi/p/9474529.html
今日推荐