P1600 [NOIP2016 提高组] 天天爱跑步 题解

线段树合并 + 树上差分的好题。

考虑对每个点开一个值域线段树维护值而不是深度,值域 [n,n],预处理 LCA 和 每个点的深度。

st 的路径拆成两段,一段是 slca

image

注意到此时 slca 的路径上所有点 x,设其被走到的时间为 tx,则所有 x 满足 depx+tx 为定值。

据此我们在 slca 这一段链上每个点的线段树 deps 的对应位置加 1,树上差分即可。

另一端是 lcat

image

注意到此时 lcat 的路径上所有点 x,还是设其被走到的时间为 tx,则所有 x 满足 depxtx 为定值。

据此我们在 lcat 这一段链上每个点线段树 dept(dept+deps2deplca) 的对应位置加 1,树上差分即可。

然而这并不是简单树上差分就好了,因为 lca 两个条件同时满足,而这么做 lca 这个点查询时会被算 2 次,所以我们在 s,t 两个点上打完标记后 lca 这个点对应位置取一个减一,然后其父亲对应位置取另一个减一即可。

实际上上述操作就是将 s 到 lca 的下端作为第一种情况,lcat 作为第二种情况统计。

树上差分之后做线段树合并即可,查询时询问 depx+wxdepxwx 两个位置的值。

可以写两棵线段树分别维护,但是也可以只写一棵因为上述推断条件时是充要条件,写一棵时注意 wx=0 只查询一次。

线段树维护的值域为 [n,n],当然也可以整体平移成 [0,2n]

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;
}
posted @   Plozia  阅读(76)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示