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

闲话

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

题目链接

洛谷

题解

首先对变量进行声明

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

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


Case 1:观测点在起点到\(LCA\)的路径上

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

\[dep[s]-dep[g]=w[g]\]

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

\[dep[s]=dep[g]+w[g]\]

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

为什么可以差分?

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

Case 2:观测点在\(LCA\)到终点的路径上

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

\[dep[s]-2dep[lca]=w[g]-dep[g]\]

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

tips

  • Case 2中的差值可能<0,需要整体右移,查询的时候也要整体右移,大小自己控制。
  • 不要两次都把\(-1\)的标记都打在\(lca\)上,这样会减掉\(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;
}
posted @ 2019-05-02 09:42 chhokmah 阅读(...) 评论(...) 编辑 收藏