[BZOJ3611][Heoi2014]大工程(虚树+树形DP)

本文介绍了一种基于虚树构建的方法来解决一类涉及到最短路径、最长路径及边权和的问题。通过定义一系列辅助变量并运用DFS遍历,文章详细展示了如何利用DP算法高效地计算出关键点对数、关键点对的深度和等指标。

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

看到这样的问题,还是先构建出虚树。以下dep[u]u原树中的深度。
对于求最短距离和最长距离,实际上就类似于求最短链和最长链的问题,记一个最小(大)值和一个次小(大)值进行DP求出。
求边权和,记size[u]u的子树内关键点的个数,f[u]u的子树内所有关键点在原树中的深度之和,
g[u]=size[u](size[u]1)2(也就是u的子树内关键点的对数)
h[u]=(size[u]1)f[u](可以理解为u的子树内所有关键点对的深度和)
以上四个值可以一遍DFS算出。
那么可以得出,LCA为u的关键点对数cnt[u]=g[u]vson[u]g[v]
LCA为u的所有关键点对的深度和sum[u]=h[u]vson[u]h[v]
由于点对(u,v)的距离为u的深度+v的深度2LCA(u,v)的深度,因此以u为LCA的点对距离和为sum[u]2cnt[u]dep[u]
此题细节较多。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1e6 + 5, M = 2e6 + 5, LogN = 22, INF = 0x3f3f3f3f;
int n, ecnt, nxt[M], adj[N], go[M], dep[N], fa[N][LogN], vir[N], vn, stk[N],
par[N], vdn, dfn[N], times, aux[N], ecnt2, nxt2[M], adj2[N], go2[M],
Max, Min, sze[N], maxd[N], mind[N]; ll sum, deps[N], dsum[N], dcnt[N];
bool isvir[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void add_edge2(int u, int v) {
    nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
}
void dfs(int u, int fu) {
    int i; dep[u] = dep[fa[u][0] = fu] + 1; dfn[u] = ++times;
    for (i = 0; i <= 19; i++)
        fa[u][i + 1] = fa[fa[u][i]][i];
    for (int e = adj[u], v; e; e = nxt[e])
        if ((v = go[e]) != fu) dfs(v, u);
}
int lca(int u, int v) {
    int i; if (dep[u] < dep[v]) swap(u, v);
    for (i = 20; i >= 0; i--) {
        if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
        if (u == v) return u;
    }
    for (i = 20; i >= 0; i--)
        if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
bool comp(int u, int v) {
    return dfn[u] < dfn[v];
}
void build() {
    int i, top = 0; vdn = vn;
    sort(vir + 1, vir + vn + 1, comp);
    for (i = 1; i <= vdn; i++) {
        int u = vir[i]; if (!top) {par[stk[++top] = u] = 0; continue;}
        int w = lca(stk[top], u);
        while (dep[stk[top]] > dep[w]) {
            if (dep[stk[top - 1]] < dep[w]) par[stk[top]] = w;
            top--;
        }
        if (w != stk[top]) vir[++vn] = w, par[w] = stk[top], stk[++top] = w;
        par[stk[++top] = u] = w;
    }
    sort(vir + 1, vir + vn + 1, comp);
    ecnt2 = 0; for (i = 1; i <= vn; i++) adj2[vir[i]] = 0;
    for (i = 2; i <= vn; i++) add_edge2(par[vir[i]], vir[i]);
}
void dfs2(int u, int fu) {
    sze[u] = isvir[u]; ll ds, dc; deps[u] = isvir[u] ? dep[u] : 0;
    maxd[u] = -INF; mind[u] = INF; int nmax = -INF, nmin = INF;
    for (int e = adj2[u], v; e; e = nxt2[e])
        dfs2(v = go2[e], u), sze[u] += sze[v], deps[u] += deps[v];
    ds = dsum[u] = 1ll * (sze[u] - 1) * deps[u];
    dc = dcnt[u] = 1ll * sze[u] * (sze[u] - 1) >> 1;
    for (int e = adj2[u], v; e; e = nxt2[e]) {
        dc -= dcnt[v = go2[e]]; ds -= dsum[v];
        if (maxd[v] > maxd[u]) nmax = maxd[u], maxd[u] = maxd[v];
        else if (maxd[v] > nmax) nmax = maxd[v];
        if (mind[v] < mind[u]) nmin = mind[u], mind[u] = mind[v];
        else if (mind[v] < nmin) nmin = mind[v];
    }
    sum += ds - dc * dep[u] * 2;
    if (maxd[u] != -INF && nmax != -INF)
        Max = max(Max, maxd[u] + nmax - dep[u] * 2);
    if (mind[u] != INF && nmin != INF)
        Min = min(Min, mind[u] + nmin - dep[u] * 2);
    if (isvir[u]) {
        Max = max(Max, maxd[u] - dep[u]); Min = min(Min, mind[u] - dep[u]);
        maxd[u] = max(maxd[u], dep[u]); mind[u] = min(mind[u], dep[u]);
    }
}
int main() {
    int i, x, y, q; n = read(); for (i = 1; i < n; i++) x = read(), y = read(),
    add_edge(x, y); dfs(1, 0); q = read(); while (q--) {
        vdn = vn = read(); for (i = 1; i <= vn; i++)
            isvir[aux[i] = vir[i] = read()] = 1;
        sum = 0; Max = -INF; Min = INF;
        build(); dfs2(vir[1], 0); printf("%lld %d %d\n", sum, Min, Max);
        for (i = 1; i <= vdn; i++) isvir[aux[i]] = 0;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值