P1600 [NOIP2016 提高组] 天天爱跑步 题解
线段树合并 + 树上差分的好题。
考虑对每个点开一个值域线段树维护值而不是深度,值域 ,预处理 LCA 和 每个点的深度。
将 的路径拆成两段,一段是 :
注意到此时 的路径上所有点 ,设其被走到的时间为 ,则所有 满足 为定值。
据此我们在 这一段链上每个点的线段树 的对应位置加 1,树上差分即可。
另一端是 :
注意到此时 的路径上所有点 ,还是设其被走到的时间为 ,则所有 满足 为定值。
据此我们在 这一段链上每个点线段树 的对应位置加 1,树上差分即可。
然而这并不是简单树上差分就好了,因为 lca 两个条件同时满足,而这么做 lca 这个点查询时会被算 2 次,所以我们在 s,t 两个点上打完标记后 lca 这个点对应位置取一个减一,然后其父亲对应位置取另一个减一即可。
实际上上述操作就是将 s 到 lca 的下端作为第一种情况, 作为第二种情况统计。
树上差分之后做线段树合并即可,查询时询问 和 两个位置的值。
可以写两棵线段树分别维护,但是也可以只写一棵因为上述推断条件时是充要条件,写一棵时注意 只查询一次。
线段树维护的值域为 ,当然也可以整体平移成 。
GitHub:CodeBase-of-Plozia
Code:
/*
========= Plozia =========
Author:Plozia
Problem:P1600 [NOIP2016 提高组] 天天爱跑步
Date:2022/5/10
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 3e5 + 5;
int n, m, Head[MAXN], cntEdge, dep[MAXN], fa[MAXN][21], w[MAXN], Root[MAXN], ans[MAXN], cntSgT;
struct node { int To, Next; } Edge[MAXN << 1];
struct SgT { int val, ls, rs; } tree[MAXN * 40];
#define val(p) tree[p].val
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48);
return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }
void addEdge(int x, int y) { Edge[++cntEdge] = (node){y, Head[x]}; Head[x] = cntEdge; }
void dfs1(int now, int father)
{
dep[now] = dep[father] + 1; fa[now][0] = father;
for (int i = Head[now]; i; i = Edge[i].Next)
{
int u = Edge[i].To; if (u == father) continue ; dfs1(u, now);
}
}
void init()
{
for (int j = 1; j <= 20; ++j)
for (int i = 1; i <= n; ++i)
fa[i][j] = fa[fa[i][j - 1]][j - 1];
}
int LCA(int x, int y)
{
if (dep[x] < dep[y]) std::swap(x, y);
for (int i = 20; i >= 0; --i)
if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
if (x == y) return x;
for (int i = 20; i >= 0; --i)
if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
void Insert(int &p, int x, int k, int lp, int rp)
{
if (!p) p = ++cntSgT; if (x < lp || x > rp) return ;
if (lp == rp) { val(p) += k; return ; }
int mid = (lp + rp) >> 1;
if (x <= mid) Insert(ls(p), x, k, lp, mid);
else Insert(rs(p), x, k, mid + 1, rp);
}
void Merge(int &p1, int p2, int lp, int rp)
{
if (!p1 || !p2) { p1 = p1 + p2; return ; }
if (lp == rp) { val(p1) += val(p2); return ; }
int mid = (lp + rp) >> 1;
Merge(ls(p1), ls(p2), lp, mid);
Merge(rs(p1), rs(p2), mid + 1, rp);
}
int Ask(int p, int x, int lp, int rp)
{
if (!p || x < lp || x > rp) return 0;
if (lp == rp) return val(p);
int mid = (lp + rp) >> 1;
if (x <= mid) return Ask(ls(p), x, lp, mid);
else return Ask(rs(p), x, mid + 1, rp);
}
void dfs2(int now, int father)
{
for (int i = Head[now]; i; i = Edge[i].Next)
{
int u = Edge[i].To; if (u == father) continue ;
dfs2(u, now); Merge(Root[now], Root[u], -n, n);
}
ans[now] = Ask(Root[now], dep[now] + w[now], -n, n);
if (w[now] != 0) ans[now] += Ask(Root[now], dep[now] - w[now], -n, n);
}
int main()
{
n = Read(), m = Read();
for (int i = 1; i < n; ++i) { int x = Read(), y = Read(); addEdge(x, y); addEdge(y, x); }
dfs1(1, 0); init();
for (int i = 1; i <= n; ++i) w[i] = Read();
for (int i = 1; i <= m; ++i)
{
int x = Read(), y = Read(), lca = LCA(x, y);
Insert(Root[x], dep[x], 1, -n, n);
Insert(Root[y], dep[y] - (dep[x] + dep[y] - 2 * dep[lca]), 1, -n, n);
Insert(Root[lca], dep[x], -1, -n, n);
Insert(Root[fa[lca][0]], dep[y] - (dep[x] + dep[y] - 2 * dep[lca]), -1, -n, n);
}
dfs2(1, 1);
for (int i = 1; i <= n; ++i) printf("%d%c", ans[i], " \n"[i == n]);
return 0;
}
标签:
基础图论/树算法
, 线段树相关,线段树合并/分治/分裂
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战