bzoj 3631: [JLOI2014]松鼠的新家(LCA+树上差分)

3631: [JLOI2014]松鼠的新家

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 2059   Solved: 1030
[ Submit][ Status][ Discuss]

Description

松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在“树”上。松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去a1,再去a2,……,最后到an,去参观新家。
可是这样会导致维尼重复走很多房间,懒惰的维尼不听地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。维尼是个馋家伙,立马就答应了。
现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。因为松鼠参观指南上的最后一个房间an是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。

Input

第一行一个整数n,表示房间个数
第二行n个整数,依次描述a1-an
接下来n-1行,每行两个整数x,y,表示标号x和y的两个房间之间有树枝相连。

Output

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

Sample Input

5
1 4 5 3 2
1 2
2 4
2 3
4 5

Sample Output

1
2
1
2
1


思路很简单也很好想:令点p为x和y的最近公共祖先

从x走到y一定是先从x走到p,然后再从p走到y(p点会被算两次所以别忘记下面in[p]--)

所以只需要in[x]++, in[y]++, in[p]--, in[fa[p]--]打标记,最后树形dp向上传递求一波就好

主要是如何O(n)离线处理所有相邻两房间的公共祖先了


注意除了根(第一个参观的房间)以外,其他点最后答案都要减1,因为每个房间会同时是起点和终点所以都会被多算一次,然后题目中说最后一个房间是餐厅所以那颗糖可以省去

#include<stdio.h>
#include<vector>
#include<string.h>
#include<algorithm>
using namespace std;
vector<int> G[300005], Q[300005];
int in[300005], ufs[300005], fa[300005], ans[300005], flag[300005];
int Find(int x)
{
	if(ufs[x]==0)
		return x;
	return ufs[x] = Find(ufs[x]);
}
void Sech(int u, int p)
{
	int i, v, t1, t2;
	fa[u] = p;
	flag[u] = 1;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==p)
			continue;
		Sech(v, u);
		t1 = Find(u);
		t2 = Find(v);
		if(t1!=t2)
			ufs[t2] = t1;
	}
	for(i=0;i<Q[u].size();i++)
	{
		v = Q[u][i];
		if(flag[v])
		{
			if(Find(v)==u)				//如果v点和u点的最近公共祖先正好是u点,那么u和v会被重复计算,所以要跳过一次
				continue;
			in[u]++, in[v]++;
			in[Find(v)]--;
			in[fa[Find(v)]]--;
		}
	}
}
int TAT(int u, int p)
{
	int i, v;
	ans[u] = in[u];
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==p)
			continue;
		ans[u] += TAT(v, u);
	}
	return ans[u];
}
int main(void)
{
	int n, i, x, y, root;
	while(scanf("%d", &n)!=EOF)
	{
		for(i=1;i<=n;i++)
			Q[i].clear(), G[i].clear();
		for(i=1;i<=n;i++)
		{
			scanf("%d", &x);
			if(i>1)
			{
				Q[y].push_back(x);
				Q[x].push_back(y);
			}
			else
				root = x;
			y = x;
		}
		for(i=1;i<=n-1;i++)
		{
			scanf("%d%d", &x, &y);
			G[x].push_back(y);
			G[y].push_back(x);
		}
		memset(flag, 0, sizeof(flag));
		memset(ufs, 0, sizeof(ufs));
		memset(in, 0, sizeof(in));
		Sech(root, 0);
		TAT(root, -1);
		for(i=1;i<=n;i++)
		{
			if(i==root)
				printf("%d\n", ans[i]);
			else
				printf("%d\n", ans[i]-1);
		}
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值