[日常训练] Tree

分析 树形DP

  • 经典的模型依旧只会暴力,果然蒟蒻呀
  • f[u][j] 表示从点 u 出发,在u的子树中经过 j 个点最后回到点u的最小距离和
  • g[u][j] 表示从点 u 出发,在u的子树中经过 j 个点最后停在任意一点(其实也相当于从u的子树中任意一点出发,经过 j 个点最后回到点u)的最小距离和
  • h[u][j] 表示在 u 的子树中从任意一点出发,经过j个点并保证经过点 u ,最后停在任意一点的最小距离和
  • 显然f,g,h都是由 u 的子节点v转移过来,则我们可以得到如下转移(为了描述方便,设已经处理完的 u 的子节点的子树集合为A,当前处理的子节点 v 的子树为B,用 表示每种转移所对应的在树上走的方案)
  • 对于 f[u][j]
    • f[u][j+k]=min(f[v][k]+f[u][j]+2dis(u,v))(uAuBu)
  • 对于 g[u][j]
    • g[u][j+k]=min(g[v][k]+f[u][j]+dis(u,v))(uAuB)
    • g[u][j+k]=min(f[v][k]+g[u][j]+2dis(u,v))(uBuA)
  • 对于 h[u][j]
    • h[u][j+k]=min(f[v][k]+h[u][j]+2dis(u,v))(AuBuA)
    • h[u][j+k]=min(h[v][k]+f[u][j]+2dis(u,v))(BuAuB)
    • h[u][j+k]=min(g[v][k]+g[u][j]+dis(u,v))(AuB)
  • 时间复杂度的证明也是比较经典了,每次枚举的是 szeuszev ,相当于每次从 A,B 中各任选一点,它们的 LCA u ,这样的点对枚举不会重复,因此总的时间复杂度为O(n2)

代码

#include <iostream>
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cstring>

using namespace std;

namespace INOUT
{
    const int S = 1 << 20;
    char frd[S], *hed = frd + S;
    const char *tal = hed;

    inline char nxtChar()
    {
        if (hed == tal)
            fread(frd, 1, S, stdin), hed = frd;
        return *hed++;
    }

    inline int get()
    {
        char ch; int res = 0; bool flag = false;
        while (!isdigit(ch = nxtChar()) && ch != '-');
        (ch == '-' ? flag = true : res = ch ^ 48);
        while (isdigit(ch = nxtChar()))
            res = res * 10 + ch - 48;
        return flag ? -res : res;
    }
};
using namespace INOUT;

const int Maxn = 0x3f3f3f3f;
const int N = 3005;
int n, Ans = Maxn, K, sze[N];
int f[N][N], g[N][N], h[N][N];
int ff[N][N], gg[N][N], hh[N][N];

struct Edge
{
    int to, cst; Edge *nxt;
}p[N << 1], *T = p, *lst[N];

inline void LinkEdge(int x, int y, int z)
{
    (++T)->nxt = lst[x]; lst[x] = T; T->to = y; T->cst = z;
    (++T)->nxt = lst[y]; lst[y] = T; T->to = x; T->cst = z;
}

inline int Min(int x, int y) {return x < y ? x : y;}
inline void CkMin(int &x, int y) {if (x > y) x = y;}

inline void Dfs(int u, int fa)
{
    sze[u] = 1;
    h[u][1] = g[u][1] = f[u][1] = 0;

    for (Edge *e = lst[u]; e; e = e->nxt)
    {
        int v = e->to;
        if (v == fa) continue;
        Dfs(v, u); 

        for (int j = 1, jm = sze[u] + sze[v]; j <= jm; ++j)
        {
            ff[u][j] = f[u][j];
            gg[u][j] = g[u][j];
            hh[u][j] = h[u][j];
        }

        int L1 = e->cst, L2 = e->cst << 1;

        for (int j = 1; j <= sze[u]; ++j)
            for (int k = 1; k <= sze[v]; ++k)
            {
                CkMin(ff[u][j + k], f[v][k] + f[u][j] + L2);
                CkMin(gg[u][j + k], g[v][k] + f[u][j] + L1);
                CkMin(gg[u][j + k], f[v][k] + g[u][j] + L2);
                CkMin(hh[u][j + k], f[v][k] + h[u][j] + L2);
                CkMin(hh[u][j + k], h[v][k] + f[u][j] + L2);
                CkMin(hh[u][j + k], g[v][k] + g[u][j] + L1);
            }

        for (int j = 1, jm = sze[u] + sze[v]; j <= jm; ++j)
        {
            f[u][j] = ff[u][j];
            g[u][j] = gg[u][j];
            h[u][j] = hh[u][j];
        }

        sze[u] += sze[v];
    }

    CkMin(Ans, h[u][K]);
}

int main()
{
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);

    n = get(); K = get(); int x, y;
    for (int i = 1; i < n; ++i)
    {
        x = get(); y = get();
        LinkEdge(x, y, get());
    }

    memset(f, Maxn, sizeof(f));
    memset(g, Maxn, sizeof(g));
    memset(h, Maxn, sizeof(h));

    Dfs(1, 0);
    printf("%d\n", Ans);

    fclose(stdin); fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值