洛谷 P3258 [JLOI2014]松鼠的新家 树上差分

2 篇文章 0 订阅
2 篇文章 0 订阅

缘起

【1】中我们学习了树上差分,并且a了一个裸的点差分. 现在继续树上差分~ 洛谷 P3258 [JLOI2014]松鼠的新家

分析

题目描述
松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,
且俩个房间之间的路线都是唯一的。天哪,他居然真的住在”树“上。

松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去a1,再去
a2,......,最后到an,去参观新家。可是这样会导致维尼重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告
诉他,每走到一个房间,他就可以从房间拿一块糖果吃。

维尼是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。

因为松鼠参观指南上的最后一个房间an是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。

输入格式
第一行一个整数n,表示房间个数第二行n个整数,依次描述a1-an

接下来n-1行,每行两个整数x,y,表示标号x和y的两个房间之间有树枝相连。

输出格式
一共n行,第i行输出标号为i的房间至少需要放多少个糖果,才能让维尼有糖果吃。

输入输出样例
输入 #1复制
5
1 4 5 3 2
1 2
2 4
2 3
4 5
输出 #1复制
1
2
1
2
1
说明/提示
2<= n <=300000

裸的点差分.

显然,小熊维尼的路线是 a1—a2、a2—a3、…a_{n-1}–an, 这些都是树上路径. 树上路径会造成一些树上节点的覆盖. 只需差分完毕之后dfs一波即可~ 但是注意,an这个点不需要放置糖果的意思是最后到达an的时候不需要吃糖果了,但是中途其他边可能路过an, 还是要放糖果的~ 所以最后 b[an]-- 即可. 但是从ai(i>1)到a\_{i+1} a[i]是不需要再放糖果的. 所以只要i不等于a[1](特别的, an 就不等于a[1]), 最后dfs完了之后就要自减1, 只是an和其他a[2,…,n-1]自减的原因不一样, an是因为最后不需要准备糖果,而a[i= 2,…,n-1]自减1是因为从ai出发不再需要放糖果.

//#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
//#define LOCAL
const int maxn = 3e5+5, maxl = 21;
int n, a[maxn], cnt, head[maxn], fa[maxn][maxl], dep[maxn], b[maxn];
bool v[maxn];
struct Arc
{
	int from, to, nxt;
}g[maxn << 1];

void addarc(int from, int to)
{
	g[cnt].from = from, g[cnt].to = to, g[cnt].nxt = head[from];
	head[from] = cnt++;
}

void dfs(int cur)
{
	v[cur] = true;
	for (int h = head[cur], to; ~h; h = g[h].nxt)
	{
		to = g[h].to;
		if (v[to])
		{
			continue;
		}
		fa[to][0] = cur;
		dep[to] = dep[cur] + 1;
		dfs(to);
	}
}

int lca(int x, int y)
{
	if (dep[x] > dep[y])
	{
		swap(x, y);
	}
	for (int j = maxl - 1; ~j; j--)
	{
		if (dep[fa[y][j]] >= dep[x])
		{
			y = fa[y][j];
		}
	}
	if (x == y)
	{
		return x;
	}
	for (int j = maxl - 1; ~j; j--)
	{
		if (fa[x][j] ^ fa[y][j])
		{
			x = fa[x][j];
			y = fa[y][j];
		}
	}
	return fa[x][0];
}

void cf(int x, int y)
{
	int a = lca(x, y);
	++b[x];
	++b[y];
	--b[a];
	--b[fa[a][0]];
}

void dfs1(int cur)
{
	v[cur] = true;
	for (int h = head[cur], to; ~h; h = g[h].nxt)
	{
		to = g[h].to;
		if (v[to])
		{
			continue;
		}
		dfs1(to);
		b[cur] += b[to];
	}
}

int main()
{
#ifdef LOCAL
	freopen("d:\\data.in","r",stdin);
//	freopen("d:\\my.out", "w", stdout);
#endif
	memset(head, -1, sizeof(head));
	scanf("%d", &n);
	for (int i = 1; i<=n; i++)
	{
		scanf("%d", a + i);
	}
	int x, y;
	for (int i = 1;i < n; i++)
	{
		scanf("%d%d", &x, &y);
		addarc(x, y);
		addarc(y, x);
	}
	dep[1] = 1;
	dfs(1);
	for (int j = 1, pa; j < maxl; j++)
	{
		for (int i = 1; i <= n; i++)
		{
			pa = fa[i][j - 1];
			fa[i][j] = fa[pa][j - 1];
		}
	}
	for (int i = 1; i < n; i++)
	{
		x = a[i];
		y = a[i + 1];
		cf(x, y);
	}
	memset(v, 0, sizeof(v));
	dfs1(1);
	for (int i = 1; i <= n; i++)
	{
		if (i ^ a[1])
		{
			--b[i];
		}
		printf("%d\n", b[i]);
	}
	return 0;
}

ac情况

所属题目
P3258 [JLOI2014]松鼠的新家
评测状态
Accepted
评测分数
100
编程语言
C++
代码长度
1.96KB
用时
934ms
内存
43.81MB

参考

【1】https://blog.csdn.net/anfengliuzhiwu/article/details/104434740

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值