P1600题解

来一发线段树合并的题解。

脑子不够,数据结构来凑

GXYZOJ这题到现在是不是只有我一个人拿线段树合并过了?

树上路径的问题大多都要转化为 LCA 问题。找一个任意根,先预处理LCA,记每个个LCA pi ,那每个玩家走的路径就可以转化为sipi  pisi

由于增加贡献的类型是动态变化的,我们不能用暴力数据结构(线段树:你***)这样的东西直接区间处理,而是要转化为树上差分来解决。

具体地,考虑对每一个观察哨进行处理,对于每一个观察哨我们开一个桶,下面我们进行分类nei讨论nen

(下面的式子是好得出的,理解不了建议自己画图理解)。

对于每个在路径st上的观察哨k:

  1. ksp上:那么deps=depk+wk

对于这种情况,我们差分时每次给桶里增加的值是deps,在每个k处查询桶里depk+wk值的数量。

  1. kpt上,那么2×deppdeps=depkwk

对于这种情况,我们差分时每次给桶里增加的值是2×deppdeps,在每个k处查询桶里depkws值的数量。

啊注意这样的话值域可能为负(即depk<ws),需要整体平移值域(加n即可)。

然后怎么统计答案?

这是桶玩家最难受的地方,但是线段树合并玩家最高兴的地方

一般地,刚才的桶我们要对每个节点开vector,然后再考虑接下来如何统计。

但是我们发现这个桶的本质维护的是值域,且一个点的贡献只可能在它的子树中,且子树中所有满足条件的点都会成为它的贡献

看到值域,看到答案中底层对顶层的后效性,这TM不就是线段树合并裸题吗?

统计答案时dfs正常遍历整棵树,然后从下到上依次合并,针对遍历到的每一个节点查询上面的两个情况,统计答案即可。

灰常的暴力,但时间复杂度灰常优秀的O(nlogn)

上代码,不懂看注释。

//P1600 [NOIP2016] 天天爱跑步
//脑子不够数据结构来凑
//对于每个节点建一棵权值线段树,略微LCA+差分一下即可
//很暴力的思想,暴力实现即可
//然后?  没了。
#include <bits/stdc++.h>
#define N 300001
#define M 20
using namespace std;
int n, m;
//链式前向星存图
struct Edge {
    int to, nxt;
} e[N << 1];
int head[N], cnt;
void add(int u, int v) {
    e[++cnt].to = v;
    e[cnt].nxt = head[u];
    head[u] = cnt;
}
int dep[N];
//倍增LCA用的数组,即每个节点x的2^i个祖先
int anc[N][M];
void dfs(int x, int fa, int d) {
    dep[x] = d;
    anc[x][0] = fa;
    for (int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if (y == fa)
            continue;
        dfs(y, x, d + 1);
    }
}
void init() {
    anc[1][0] = -1;
    dfs(1, -1, 1);
   //正常递推
    for (int j = 1; j < M; j++)
        for (int i = 1; i <= n; i++)
            anc[i][j] = anc[anc[i][j - 1]][j - 1];
}
//LCA,不懂建议自行复习
int LCA(int x, int y) {
    if (dep[x] < dep[y])
        swap(x, y);
    for (int i = M - 1; i >= 0; i--)
        if (dep[x] - (1 << i) >= dep[y])
            x = anc[x][i];
    if (x == y)
        return x;
    for (int i = M - 1; i >= 0; i--)
        if (anc[x][i] != anc[y][i]) {
            x = anc[x][i];
            y = anc[y][i];
        }
    return anc[x][0];
}

//动态开点线段树,不会的建议自行学习
struct Node {
    int lc, rc;
    int sum;
} t[N * 60]; // 开 2Nlog(2N)
#define lc(i) t[i].lc
#define rc(i) t[i].rc
#define sum(i) t[i].sum
#define mid ((l + r) >> 1)//我就是懒好吧
int tot = 1;
void push_up(int p) {
    sum(p) = sum(lc(p)) + sum(rc(p));
}
void point_add(int &p, int l, int r, int x, int val) {
    if (!p)
        p = ++tot;
    if (l == r) {
        sum(p) += val;
        return;
    }
    if (x <= mid)
        point_add(lc(p), l, mid, x, val);
    else
        point_add(rc(p), mid + 1, r, x, val);
    push_up(p);
    return;
}
int query(int p, int l, int r, int x) {
    if (!p)
        return 0;
    if (l == r)
        return sum(p);
    if (x <= mid)
        return query(lc(p), l, mid, x);
    else
        return query(rc(p), mid + 1, r, x);
}
//也不知道为啥,这里传参必须传(p, q, l, r)四个参数,只传(p, q)过不了第二个样例qwq
int Merge(int p, int q, int l, int r) {
    if (!p || !q)
        return p + q;
    if (l == r) {
        sum(p) += sum(q);
        return p;
    } else
        lc(p) = Merge(lc(p), lc(q), l, mid);
    rc(p) = Merge(rc(p), rc(q), mid + 1, r);
    return p;
}
int w[N];
int ans[N];
int rt[N];//存每一个点在线段树里的根节点编号
void join(int x) {
    for (int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if (y == anc[x][0])
            continue;
        join(y);
        rt[x] = Merge(rt[x], rt[y], 1, (n << 1));
    }
    if (w[x] && n + dep[x] + w[x] <= 2 * n)//不能越界
        ans[x] += query(rt[x], 1, (n << 1), n + dep[x] + w[x]);
    ans[x] += query(rt[x], 1, (n << 1), n + dep[x] - w[x]);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
        add(v, u);
    }
    init();
    for (int i = 1; i <= n; i++)
        scanf("%d", &w[i]);
    while (m--) {
        int s, t;
        scanf("%d%d", &s, &t);
        int lca = LCA(s, t);
       //平移了值域
        point_add(rt[s], 1, (n << 1), n + dep[s], 1);
        point_add(rt[t], 1, (n << 1), n + dep[lca] * 2 - dep[s], 1);
        point_add(rt[lca], 1, (n << 1), n + dep[s], -1);//差分
        point_add(rt[anc[lca][0]], 1, (n << 1), n + dep[lca] * 2 - dep[s], -1);
    }
    join(1);
    for (int i = 1; i <= n; i++)
        cout << ans[i] << " ";
    puts("");
    return 0;
}
posted @   长安19路  阅读(10)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示