首页 > 其他 > 详细

51nod1803 森林直径

时间:2019-10-13 09:25:33      阅读:87      评论:0      收藏:0      [点我收藏+]

[传送门]

考虑计算直径的 dp 方法。
$d[u]$ 表示以 $u$ 为根的子树能 $u$ 能走到的最远距离
$dp[u]$ 表示以 $u$ 为根的子树的直径
那么每次dfs一个子节点后
$dp[u] = max(d[u] + d[v] + 1, dp[u])$
$d[u] = max(d[v] + 1, d[u])$
注意顺序一反就错了。
这里并没有考虑到 $u$ 是某个节点的儿子是,路径可以往父节点走的情况,但是这是对的。
因为如果这种情况存在,那么我们在递归回父节点是就可以更新到。所以直径取最后 dp 数组的最大值是对的。
回到这个题,因为数据的生成方式,树高不超过 $log n$,并且第 $i$ 条边一定有 第 $i + 1$个节点。
如果从 $[x, n - 1]$ 这棵树已经建好了,那么加入第 $x - 1$ 条边就是把 $p[x - 1]$ 和 $x$ 这两个节点形成的连通块进行合并。
深度不超过 $log n$,那么可以暴力一点。
将询问离线。按 $r$ 从大到小排序,考虑倒序建这棵树。
$f[u][i]$ 表示以 $u$ 为根的子树内 $u$ 能到达的最远距离为 $i$ 时 $r$ 的最小值。即在 $[l, f[u][i]]$ $u$ 能往下走 $i$ 步。
$g[i]$ 表示树的直径至少为 $i$ 时 最小的 $r$。即在 $[l, g[i]]$ 树的直径至少为 $i$。
倒序加入一条边,相当于两棵树合并,那么就先更新 $g$ 数组,具体更新方式如 $dp$ 的更新方式,分别枚举高度进行合并。
$g[i + j + 1] = min(g[i + j + 1], max(f[u][i], f[v][i], v - 1))$ $v-1$ 即为当前加入的边。
在更新 $u$,即 $u$ 为 $v$ 的父亲。
$f[u][i] = min(f[u][i], max(f[v][i - 1], v - 1))$
求答案就枚举高度看 $g$ 数组是否小于当前 $r$

 

技术分享图片
#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 7;
const int dep = 40;

struct In { 
    int l, r;
    bool operator < (const In &rhs) const {
        if (l == rhs.l) return r > rhs.r;
        return l > rhs.l;
    }
} que[N];

int f[N][dep + 7];
int g[dep * 3];
int p[N], n;

void add(int u, int v) {
    for (int i = 0; i < dep; i++) if (f[u][i] < n)
        for (int j = 0; j < dep; j++) 
            g[i + j + 1] = min(g[i + j + 1], max(f[u][i], max(f[v][j], v - 1)));
    for (int i = 1; i < dep; i++)
        f[u][i] = min(f[u][i], max(f[v][i - 1], v - 1));
}

int main() {
    scanf("%d", &n);
    memset(f, 0x3f, sizeof(f));
    for (int i = 1, x; i < n; i++) 
        scanf("%d%d", p + i, &x), f[i][0] = 0;
    f[n][0] = 0;
    memset(g, 0x3f, sizeof(g));
    int q;
    scanf("%d", &q);
    for (int i = 1; i <= q; i++)
        scanf("%d%d", &que[i].l, &que[i].r);
    sort(que + 1, que + 1 + q);
    int now = n - 1;
    int ans = 0;
    for (int i = 1; i <= q; i++) {
        while (now && now >= que[i].l) {
            add(p[now], now + 1);
            now--;
        }
        for (int k = dep * 2; k >= 0; k--) {
            if (g[k] <= que[i].r) {
                ans += k;
                break;
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}
View Code

 

51nod1803 森林直径

原文:https://www.cnblogs.com/Mrzdtz220/p/11664638.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!