2015编程之美初赛第一场 A 彩色的树

时间限制: 2000ms
单点时限: 1000ms
内存限制: 256MB

描述

给定一棵n个节点的树,节点编号为1, 2, …, n。树中有n - 1条边,任意两个节点间恰好有一条路径。这是一棵彩色的树,每个节点恰好可以染一种颜色。初始时,所有节点的颜色都为0。现在需要实现两种操作:

1. 改变节点x的颜色为y;

2. 询问整棵树被划分成了多少棵颜色相同的子树。即每棵子树内的节点颜色都相同,而相邻子树的颜色不同。

输入

第一行一个整数T,表示数据组数,以下是T组数据。

每组数据第一行是n,表示树的节点个数。接下来n - 1行每行两个数i和j,表示节点i和j间有一条边。接下来是一个数q,表示操作数。之后q行,每行表示以下两种操作之一:

1. 若为"1",则询问划分的子树个数。

2. 若为"2 x y",则将节点x的颜色改为y。

输出

每组数据的第一行为"Case #X:",X为测试数据编号,从1开始。

接下来的每一行,对于每一个询问,输出一个整数,为划分成的子树个数。

数据范围

1 ≤ T ≤ 20

0 ≤ y ≤ 100000

小数据

1 ≤ n, q ≤ 5000

大数据

1 ≤ n, q ≤ 100000

样例输入
2
3
1 2
2 3
3
1
2 2 1
1
5
1 2
2 3
2 4
2 5
4
1
2 2 1
2 3 2
1
样例输出
Case #1:
1
3
Case #2:
1
5

2.解题思路:今天的比赛用dfs只过了小数据==。当这棵树退化为一条链时,肯定会TLE了。下面来学习一下AC的代码的思路。

首先把图建立起来,然后以1为根转化为有根树,在转化的时候,统计所有结点不同颜色的子结点的个数。由于颜色可能会比较多,这里可以使用map来存储每个结点的不同颜色的个数。下面我们来思考一下改变结点i的颜色会带来哪些变化。

首先思考一下怎样才会产生新的子树。(1)如果结点i是一个叶子结点,当所有叶子结点均为颜色0时,而把结点i修改为颜色1,自然会多产生一棵子树。我们可以这样来计算这棵新的树:修改前颜色0的叶子有num个,修改后自然会有num--。那么前后的差值就是新的子树的个数。这是子树的第一个来源。(2)如果结点i是一个中间的结点,假设它的颜色为0,且它有num个颜色为0的子结点。如果把结点i的颜色修改为1,自然我们知道新产生了num棵子树。此时我们也可以通过对比前后的不同得到这个值:修改前结点i的子结点中颜色为0的有cs[i][0]个(cs[i][j]表示结点i的颜色为j的子结点的个数,即num==cs[i][0]),将结点i颜色修改为1后,cs[i][1]为0,那么新产生的子树就是cs[i][0]-cs[i][1]。这是子树的第二个来源。

综上,我们发现,新的子树总是可以通过前后结点i的子结点数的变化得到。不过对于第二种情况,还要注意修改fa[i]对应的子结点情况。因为修改了i的颜色影响的是fa[i]结点的情况。

本题值得学习的地方:(1)提前写好树和图的存储模板,包括加边,记录父亲,记录子结点数等基本操作。方便后续处理。(2)注意观察执行某个操作后的变与不变。通过数学的推导来计算结果往往效率比较高。

3.代码:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define me(x) memset(x,0,sizeof(x))
#define sy system("pause")
#define maxn 100005
using namespace std;
struct edge
{
	int to, nx;
};
edge es[maxn * 2];//边集
int st[maxn], en;//en表示边的个数,st[x]是链表的头结点
map<int, int> cs[maxn];//cs[i][j]表示结点i为根且颜色为j的儿子结点的个数
int n, q, ans;
int fa[maxn], color[maxn];//fa[i]表示结点i的父结点,color[i]表示结点i的颜色


void d__add(int x, int y)
{
	edge e;
	e.to = y;
	e.nx = st[x];
	es[++en] = e;
	st[x] = en;
}
void add(int x, int y)//加边操作
{
	d__add(x, y);
	d__add(y, x);
}

void dfs(int x)//无根树转化为以x为根的有根树
{
	int i, tot = 0;
	for (i = st[x]; i; i = es[i].nx) 
	if (es[i].to != fa[x])
	{
		fa[es[i].to] = x;
		tot++;
		dfs(es[i].to);
	}
	cs[x][0] = tot;//儿子结点的个数
}
void change(int x, int y)//将结点x的颜色修改为y
{
	if (color[x] == color[fa[x]]) ans++;//假设修改后父子结点的颜色会不同,预先加1
	ans += cs[x][color[x]];//先加上所有原来颜色的儿子结点的个数
	if (fa[x])//如果x的父结点存在,更新fa[x]的子结点情况
	{
		cs[fa[x]][color[x]]--;//父结点的子结点中颜色为color[x]的减少一个
		cs[fa[x]][y]++;//颜色为y的增加一个
	}
	color[x] = y;//修改颜色
	if (color[x] == color[fa[x]]) ans--;//如果修改后的颜色和父结点的颜色一致,结果减一
	ans -= cs[x][color[x]];///减去所有目前颜色的儿子结点的个数
}
void solve(int cas)
{
	int i, a, b;
	scanf("%d", &n);
	me(st); en = 0;
	for (i = 1; i<n; i++)
	{
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	for (i = 1; i <= n; i++) cs[i].clear();
	fa[1] = 0; me(color); color[0] = -10000097;
	dfs(1); ans = 1;
	scanf("%d", &q);
	printf("Case #%d:\n", cas);
	for (i = 0; i<q; i++)
	{
		scanf("%d", &a);
		if (a == 1) printf("%d\n", ans);
		else
		{
			scanf("%d%d", &a, &b);
			change(a, b);
		}
	}
}
int main()
{
	//freopen("t.txt", "r", stdin);
	int T, i;
	scanf("%d", &T);
	for (i = 1; i <= T; i++) solve(i);
	return 0;
}


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值