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

来源:互联网 发布:mac版spss中文版下载 编辑:程序博客网 时间:2024/05/21 07:07
时间限制: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

样例输入
231 22 3312 2 1151 22 32 42 5412 2 12 3 21
样例输出
Case #1:13Case #2:15

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 100005using 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++;//假设修改后父子结点的颜色会不同,预先加1ans += 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 0