[BZOJ4987] Tree

[BZOJ4987] Tree

题目大意:从前有棵树,找出\(K\)个结点\(A_1, A_2, A_3,\cdots A_k\),使\(\sum\limits_{1 \leq i \leq K-1}{} dis(A_i,A_{i+1})\)

Solution

先画一个样例图

样例

延伸出两个结论

  1. 选出的点集一定是一个联通树

  2. 有一条链只经过一遍,剩余的边要经历两次

  • 状态:\(f[i][j][k]\)表示以\(i\)为根节点,在他的子树里面取\(j\)条边,有\(k\)个是链的端点的最小花费

树形\(DP​\)

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 3000 + 10;

int n, k, ecnt, ans = 2147483647;
int head[N], siz[N];
int f[N][N][4];

struct Edge {
    int to, next, val;
}e[N << 1];

inline void adde(int x, int y, int z) {
    e[++ecnt].to = y;
    e[ecnt].next = head[x];
    e[ecnt].val = z;
    head[x] = ecnt;
}

void dfs(int x, int fa) {
    siz[x] = 1;
    f[x][0][0] = f[x][0][1] = 0; 
    for(int i = head[x]; i; i = e[i].next) {
        if(e[i].to == fa) continue;
        dfs(e[i].to, x);
        for(int j = siz[x] - 1; j >= 0; --j)//选的路最多都是size-1 
            for(int k = siz[e[i].to] - 1; k >= 0; --k)
                for(int l = 2; l >= 0; --l)
                    for(int m = l; m >= 0; --m)//2 - (m & 1) 是如果两或零个端点都在这个子节点的话, 那么当前结点到此子节点的这段路是不包含在主链里的,要乘二 
                        f[x][j + k + 1][l] = min(f[x][j + k + 1][l], f[e[i].to][k][m] + f[x][j][l - m] + e[i].val * (2 - (m & 1))); 
        siz[x] += siz[e[i].to];//在最后加是因为,多叉树,两个两个合并 
    }
}

int main() {
    scanf("%d %d", &n, &k);
    for(int i = 1, la, lb, lc; i <= n - 1; ++i) {
        scanf("%d %d %d", &la, &lb, &lc);
        adde(la, lb, lc);
        adde(lb, la, lc);
    }
    memset(f, 0x3f, sizeof(f));
    dfs(1, 0);
    for(int i = 1; i <= n; ++i)
        for(int j = 0; j <= 2; ++j)
            ans = min(ans, f[i][k - 1][j]);
    printf("%d", ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LMSH7/p/9706085.html
今日推荐