csu 1811 Tree Intersection 2016湖南省赛 I

31 篇文章 0 订阅
19 篇文章 0 订阅

Problem

acm.csu.edu.cn/csuoj/problemset/problem?pid=1811

vjudge.net/contest/161962#problem/I

Reference

blog.csdn.net/qwb492859377/article/details/52436186

www.cnblogs.com/fightfordream/p/6801852.html

Meaning

一棵 n 个结点的树,每个结点都有一种颜色,问对与树上的每条边,删掉它之后得到的两棵树中,共有的颜色有多少种(在那两棵树中都有的颜色就是公有的颜色)

Analysis

首先规定 1 号结点为整棵树的根(其它号也可以)。

对与每一条边,就看成是某个结点于它的父结点的连边,于是,删掉这条边后两个连同块的共有颜色数,就等价于以这个结点为根的子树里共有颜色数(只有两个连通块,其中一个连通块的“公共颜色”即是两个连同块的公共颜色)。

公共颜色是什么呢?假如在其中一个连通块中有 2 个绿色结点,而原树一共有 4 个结点是绿色的,那绿色就是这两个连通块的公共颜色之一;反之,这个连通块有一个黑色结点,而原树也总共只有一个黑色结点,那黑色就是这个连通块“私有”的颜色。

怎么统计一棵子树里的共有颜色数呢?可以用线段树。先不考虑空间,对每个结点都建一棵线段树,记录共有颜色数,然后将所有子结点的树合并到父结点的树里,就得到了父结点的答案。但这么做空间太大。

但其实对每个结点都没必要真的建一整棵树,因为根结点只有一种颜色,只需要一条链,而子树的信息,可以重用子树建出来的结点,这样就可以开得下。

具体看代码理解吧,线段树新姿势啊。

Code

#include <cstdio>
#include <cstring>
using namespace std;
const int N = 100000;

struct edge
{
	int to, nxt;
} e[N<<1];

struct node
{
	int lc, rc; // 左、右子结点的位置
	int com; // 这棵树中共有颜色的数量
	int num; // 对叶子有用,表示这棵树中这种颜色的结点数
} tree[18*N]; // 存树结点的空间

int sz; // 树结点总数
int c[N+1]; // 结点i的颜色
int sum[N+1]; // 颜色i的总数
int head[N+1]; // 前向星链头
int root[N+1]; // 为第i个结点建的线段树的根所在位置
int ans[N]; // 答案数组

void add_edge(int f, int t, int id)
{
	e[id].to = t;
	e[id].nxt = head[f];
	head[f] = id;
}

inline void pushup(int x)
{
	/* 不是叶子的结点,只需记录共有颜色的数量 */
	tree[x].com = tree[tree[x].lc].com + tree[tree[x].rc].com;
}

int build(int c, int l, int r)
{
	int rt = ++sz;
	tree[rt].lc = tree[rt].rc = tree[rt].num = 0;
	if(l == r)
	{
		tree[rt].num = 1;
		tree[rt].com = tree[rt].num < sum[c];
	}
	else
	{
		int m = l + r >> 1;
		/* 不需要建整棵树,只要建一条链 */
		if(c > m)
			tree[rt].rc = build(c, m+1, r);
		else
			tree[rt].lc = build(c, l, m);
		pushup(rt);
	}
	return rt;
}

void merge(int &fa, int ch, int l, int r)
{
	if(!fa || !ch)
	{
		/* 若父结点那棵线段树的这个结点为空,
		 * 就直接重用子结点的线段树的这条链
		 */
		if(!fa)
			fa = ch;
		return;
	}
	if(l != r)
	{
		int m = l + r >> 1;
		merge(tree[fa].lc, tree[ch].lc, l, m);
		merge(tree[fa].rc, tree[ch].rc, m+1, r);
		pushup(fa);
	}
	else
	{
		/* 将子树里这种颜色的结点数加到父结点的树 */
		tree[fa].num += tree[ch].num;
		/* 这种颜色的结点不全在这棵树中,
		 * 则这种颜色是共有颜色
		 */
		tree[fa].com = tree[fa].num < sum[l];
	}
}

void dfs(int rt, int fa, int eid, int n)
{
	/* 先给根结点“建棵树” */
	root[rt] = build(c[rt], 1, n);
	for(int i=head[rt]; ~i; i=e[i].nxt)
	{
		if(e[i].to == fa)
			continue;
		/* 先递归处理子结点 */
		dfs(e[i].to, rt, i, n);
		/* 然后将子结点信息合并上来 */
		merge(root[rt], root[e[i].to], 1, n);
	}
	/* 加边时同一条边加了两次,
	 * 这个映射找出此边在输入时的序号
	 */
	if(rt != 1)
		ans[eid/2+1] = tree[root[rt]].com;
}

int main()
{
	tree[0].lc = tree[0].rc = tree[0].com = tree[0].num = 0;
	int n;
	while(~scanf("%d", &n))
	{
		memset(sum, 0, sizeof sum);
		for(int i=1; i<=n; ++i)
		{
			scanf("%d", c+i);
			++sum[c[i]];
		}
		memset(head, -1, sizeof head);
		for(int i=1, a, b, id=0; i<n; ++i)
		{
			scanf("%d%d", &a, &b);
			add_edge(a, b, id++);
			add_edge(b, a, id++);
		}
		sz = 0;
		memset(root, 0, sizeof root);
		dfs(1, 0, 0, n);
		for(int i=1; i<n; ++i)
			printf("%d\n", ans[i]);
	}
	return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: CSU飞跃手册2019是一个以服务学生发展为宗旨的学生服务项目,帮助学生实现个人成长和职业发展目标。飞跃手册提供了诸如成功学、职业规划、就业技巧以及个人发展等方面的指导,帮助学生掌握自我认知、提升职场竞争力并构建良好的人脉关系。 飞跃手册2019包括三个模块。第一个模块着重讲解如何探索自我,明确职业目标和规划职业道路。这些关键的步骤帮助学生发现自己的职业特长和兴趣爱好,从而为个人职业发展做出决策和规划。 第二个模块提供了职业发展过程中的必备技能,如招聘面试技巧、简历写作、职场沟通、决策和问题解决能力,以及管理和领导力等。这些技能帮助学生在职场中不断学习和成长,并展示自己的优势。 第三个模块则介绍如何利用社会资源和人脉关系来推动个人职业发展。它包括了网络管理、人脉建立、社交礼仪和社会责任等方面的指导。 总之,CSU飞跃手册2019是一份应对职场挑战和工作生涯的指南, 帮助学生建立自己的职业目标并实现自我发展。它讲解了个人成长、职场技能、人脉力量和社会责任等方面的内容,为学生的成功创造了一个强有力的支撑体系。 ### 回答2: CSU飞跃手册2019是一本由中国海洋大学控制科学与工程学院发行的指导学生爆炸性训练和实习的手册。该手册将学生分为不同职业兴趣组,并提供了具体的介绍、岗位培训以及实习就业方案。 该手册还包括了部分参考书目、学术论文和个人成长方向的建议,在实习期间指导学生更好地发展个人能力和提高自己的竞争力。除此之外,该手册还有一些校内外的实践活动介绍和经验分享。这些活动涵盖了多个不同领域,包括科技、创业、环保和社会公益等。 该手册还提供了一些面试技巧和求职指南,帮助学生更好地应对就业市场的挑战。此外,该手册还鼓励学生通过网络平台和社会实践等多种途径积累人脉资源,增强自己的社交和交际技巧。 总的来说,CSU飞跃手册2019为学生提供了一个全面的指导工具,帮助他们更好地理解职业规划,提高个人素质,扩展职业视野并实现自我价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值