http://hihocoder.com/problemset/problem/1156
给定一棵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
比赛时纯暴力过了小数据。后来看到岛娘说说:A map = =。想了想,知道是怎么回事了。把树转化为有根树,为每个节点开一个map,map[i][j]存储的是以i为根的子树中,颜色为j的孩子有多少个。维护这样的信息就可以解题了,因为整棵树的“颜色相同的子树的棵树”等于“与父亲颜色不同的节点数”+1,详见代码。
#include <bits/stdc++.h> using namespace std; #define ll long long const int maxn=100010; int tote; int head[maxn]; int to[maxn<<1]; int pre[maxn<<1]; int vis[maxn]; int p[maxn]; int color[maxn]; map<int,int> mp[maxn]; void addedge(int u,int v){ to[tote]=v; pre[tote]=head[u]; head[u]=tote++; to[tote]=u; pre[tote]=head[v]; head[v]=tote++; } void dfs(int u){ vis[u]=1; int cnt=0; for(int i=head[u];~i;i=pre[i]){ int v=to[i]; if(vis[v])continue; p[v]=u; dfs(v); cnt++; } mp[u][0]=cnt; } void init(){ memset(head,-1,sizeof(head)); memset(vis,0,sizeof(vis)); memset(color,0,sizeof(color)); tote=0; } int main(){ int t; cin>>t; int cas=0; while(t--){ init(); cas++; int n; cin>>n; for(int i=1;i<=n;i++)mp[i].clear(); for(int i=1;i<n;i++){ int u,v; scanf("%d%d",&u,&v); addedge(u,v); } dfs(1); printf("Case #%d:\n",cas); int q; cin>>q; int ans=1; for(int i=1;i<=q;i++){ int op; scanf("%d",&op); if(op==1){ printf("%d\n",ans); }else{ int x,y; scanf("%d%d",&x,&y); //不是根,维护当前节点与父亲关系 int px=p[x]; if(px){ if(--mp[px][color[x]]==0){ mp[px].erase(color[x]); } if(color[x]==color[px]){ if(color[px]!=y){ ans++; } } if(color[x]!=color[px]){ if(color[px]==y)ans--; } } //维护当前节点与孩子关系 if(mp[x].count(color[x])){ ans+=mp[x][color[x]]; } if(mp[x].count(y)){ ans-=mp[x][y]; } //最后变色 if(px)++mp[px][y]; color[x]=y; } } } return 0; }