「洛谷1600」「NOIP2016提高组」天天爱跑步【树上差分】

闲话

为了理清这道题目的思路,我是边写博客边做题的,qwq。

题目链接

洛谷

题解

首先对变量进行声明

dep[i]  表示i号节点的深度,是到根节点的深度
w[i]    表示i号观测点观测的时间
dfn[i]  表示i号点的dfn序
sz[i]   表示i号点的子树大小

p1
以上图为例,蓝色点表示一个玩家的起点和终点。
不妨先假设左边的点是起点,右边的点是终点,分别用 s s s t t t来表示
那么可能对答案产生贡献的点一定是在这个从 s s s t t t上的观测点。
记观测点为 g g g
对这个问题进行分类讨论


Case 1:观测点在起点到 L C A LCA LCA的路径上

p2
我们把这个情况记为观测点在 A A A种路线上。
如果这个观测点能够观测到这个起点,那么一定满足以下的式子

d e p [ s ] − d e p [ g ] = w [ g ] dep[s]-dep[g]=w[g] dep[s]dep[g]=w[g]

可以从图中观察到, d e p [ s ] − d e p [ g ] dep[s]-dep[g] dep[s]dep[g]的值就是 s s s到观测点 g g g的时间长度。
为什么不需要 ± 1 ±1 ±1,是因为时间一开始是从 0 0 0开始计数的。
换句话说,就是实际的通过时间就是通过边的数量或者是路径上经过点的数量 − 1 -1 1
对于现有式子进行变形。

d e p [ s ] = d e p [ g ] + w [ g ] dep[s]=dep[g]+w[g] dep[s]=dep[g]+w[g]

可以发现等式的右边是题目给定的定值。
那么问题就变成了对于每一个在 A A A类路径上的观测点,起点的深度等于 d e p [ g ] + w [ g ] dep[g]+w[g] dep[g]+w[g]的个数。
暴力求解这个问题的复杂度差不多是 n × ( d e p [ g ] + w [ g ] − d e p [ s ] ) n\times (dep[g]+w[g]-dep[s]) n×(dep[g]+w[g]dep[s]),明显过不了,(别忘了后面还有一个情况需要讨论)
我们可以把这个问题变一下,变成在以 g g g为根的子树内,有多少个起点满足以上的性质。
转换成子树的问题就可以用树链剖分维护了。
先扯一个常识:在树上,一棵以 u u u为根节点的子树的区间是 [ d f n [ u ] , s z [ u ] + d f n [ u ] − 1 ] [dfn[u],sz[u]+dfn[u]-1] [dfn[u],sz[u]+dfn[u]1]
问题转化成了:在区间 [ d f n [ u ] , s z [ u ] + d f n [ u ] − 1 ] [dfn[u],sz[u]+dfn[u]-1] [dfn[u],sz[u]+dfn[u]1]中有多少个深度等于 d e p [ g ] + w [ g ] dep[g]+w[g] dep[g]+w[g]的起点的个数。
可以对每一个深度建立一棵线段树。(动态开点,否则会MT飞掉)
那么问题就变成了在深度为 d e p [ g ] + w [ g ] dep[g]+w[g] dep[g]+w[g]的线段树中在区间 [ d f n [ u ] , s z [ u ] + d f n [ u ] − 1 ] [dfn[u],sz[u]+dfn[u]-1] [dfn[u],sz[u]+dfn[u]1]中有多少个起点。
如果暴力修改区间并且查询,修改的复杂度是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),来一条链就爆炸了。
考虑树上差分,其实这个东西我也想了很久,也不知道为什么可以差分,但是其实挺简单的。

为什么可以差分?

差分是什么?
差分只的前一个答案和后一个答案之间的差值。
在树上也就变成了祖先和儿子之间的关系。
先不要管线段树这个东西,会妨碍我们思考。
因为我们都知道,如果用差分计算一棵树上的答案,那么就是这颗树里面所有差分值全部 + + +起来。
在这里因为路径 s − > l c a s->lca s>lca的答案只有 s s s这个起点有贡献。
模拟一下,如果是 s s s的子树,很明显这个答案不会产生贡献。
如果是 s − > l c a s->lca s>lca的链上,这个答案会对 g g g贡献 + 1 +1 +1
如果是lca以上的祖先,那么就在 l c a lca lca上打一个 − 1 -1 1的标记。
抽象的概念就是:在这个深度上只有 s − > l c a s->lca s>lca这一段区间的答案可以 + 1 +1 +1
那么套一个线段树就可以了。

Case 2:观测点在 L C A LCA LCA到终点的路径上

下面一半就简单了,图我就不画了。
得到产生贡献的式子
d e p [ s ] + d e p [ g ] − 2 × d e p [ l c a ] = w [ g ] dep[s]+dep[g]-2\times dep[lca]=w[g] dep[s]+dep[g]2×dep[lca]=w[g]
前一半的式子其实就是求 s − > g s->g s>g的最短距离。
推导得到

d e p [ s ] − 2 d e p [ l c a ] = w [ g ] − d e p [ g ] dep[s]-2dep[lca]=w[g]-dep[g] dep[s]2dep[lca]=w[g]dep[g]

等式右边又是一个定值。
仿照上面的套路:对于深度建立线段树,打树上差分标记。
总的时间复杂度是: O ( n l o g n ) O(nlogn) O(nlogn)

tips

  • Case 2中的差值可能<0,需要整体右移,查询的时候也要整体右移,大小自己控制。
  • 不要两次都把 − 1 -1 1的标记都打在 l c a lca lca上,这样会减掉 l c a lca lca的答案,有一个标记打在 l c a lca lca的父亲上。
  • 做完一遍后要请空数组。

Code

#include <bits/stdc++.h>
using namespace std;
namespace IOstream {
    #define gc getchar
    template <typename T> inline void read(T &x) {
        x = 0; T fl = 1; char c = 0;
        for (; c < '0' || c > '9'; c = gc()) if (c == '-') fl = -1;
        for (; c >= '0' && c <= '9'; c = gc()) x = (x << 1) + (x << 3) + (c ^ 48);
        x *= fl;  
    }
    template <typename T> inline void write(T x) {
        if (x < 0) putchar('-'), x *= -1;
        if (x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
    template <typename T> inline void writeln(T x) { write(x); puts(""); }
    template <typename T> inline void writesp(T x) { write(x); putchar(' '); }
    #undef gc
} using namespace IOstream;
const int N = 3e6 + 5;
struct edge {
    int to, nt;
} E[N << 1];
struct pl {
    int s, t, lca;
} a[N];
int H[N];
int sz[N], fa[N], dep[N], son[N], top[N], dfn[N], rt[N];
int ecnt, n, m, __dfn = 0; 
int w[N], ans[N];
void add_edge(int u, int v) {
    E[++ ecnt] = (edge) {v, H[u]}; 
    H[u] = ecnt;
}
void dfs1(int u, int ft = 0) {
    fa[u] = ft; sz[u] = 1; dep[u] = dep[ft] + 1;
    int maxson = -1; 
    for (int e = H[u]; e; e = E[e].nt) {
        int v = E[e].to; 
        if (v == fa[u]) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] > maxson) maxson = sz[v], son[u] = v;
    }
}
void dfs2(int u, int tp = 1) {
    top[u] = tp; 
    dfn[u] = ++ __dfn;
    if (!son[u]) return;
    dfs2(son[u], top[u]);
    for (int e = H[u]; e; e = E[e].nt) {
        int v = E[e].to;
        if (v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}
int LCA(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}
namespace seg {
    int tot;
    struct node {
        int lc, rc, s;
    } tr[N * 10];
    void clear() { tot = 0; memset(tr, 0, sizeof(tr)); }
    void upd(int &nod, int l, int r, int k, int val) {
        if (!k) return;
        if (!nod) nod = ++ tot;
        tr[nod].s += val;
        if (l == r) return;
        int mid = (l + r) >> 1;
        if (k <= mid) upd(tr[nod].lc, l, mid, k, val);
        else upd(tr[nod].rc, mid + 1, r, k, val);
    }
    int query(int nod, int l, int r, int ql, int qr) {
        if (!nod) return 0;
        if (ql <= l && r <= qr) return tr[nod].s;
        int mid = (l + r) >> 1, res = 0;
        if (ql <= mid) res += query(tr[nod].lc, l, mid, ql, qr);
        if (qr > mid) res += query(tr[nod].rc, mid + 1, r, ql, qr);
        return res; 
    }
}
signed main() {
    read(n); read(m);
    for (int i = 1, u, v; i < n; i ++) {
        read(u); read(v); 
        add_edge(u, v); add_edge(v, u);
    }
    dep[0] = 0; dfs1(1);  
    dfs2(1); 
    for (int i = 1; i <= n; i ++) read(w[i]);
    for (int i = 1; i <= m; i ++) {
        read(a[i].s); read(a[i].t);
        a[i].lca = LCA(a[i].s, a[i].t);
    }
    seg::clear(); memset(rt, 0, sizeof(rt));
    for (int i = 1; i <= m; i ++) { 
        int root = dep[a[i].s];
        seg::upd(rt[root], 1, n, dfn[a[i].s], 1);
        seg::upd(rt[root], 1, n, dfn[fa[a[i].lca]], -1);
    } 
    for (int i = 1; i <= n; i ++) 
        ans[i] += seg::query(rt[dep[i] + w[i]], 1, n, dfn[i], sz[i] + dfn[i] - 1);
    seg::clear(); memset(rt, 0, sizeof(rt));
    for (int i = 1; i <= m; i ++) {
        int root = dep[a[i].s] - dep[a[i].lca] * 2 + 2 * n;
        seg::upd(rt[root], 1, n, dfn[a[i].t], 1);
        seg::upd(rt[root], 1, n, dfn[a[i].lca], -1);
    } 
    for (int i = 1; i <= n; i ++) 
        ans[i] += seg::query(rt[w[i] - dep[i] + 2 * n], 1, n, dfn[i], sz[i] + dfn[i] - 1);
	for (int i = 1; i <= n; i ++) 
        writesp(ans[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值