-
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
描述
给定一棵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.解题思路:今天的比赛用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;
}