给定一棵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; }
原文地址:http://blog.csdn.net/u014800748/article/details/45272031