分析:
题目要求是区间修改 区间查询,我们考虑用线段树去搞。
但是这里是在树上,而线段树是维护(线性)区间信息的。
所以我们需要对树做一个预处理,即:求出树的dfs序
dfs序就是从根节点dfs得到的一个序列,将树拍平成一条线。
比如:
这棵树的dfs序(不唯一)就是:1 4 6 3 7 10 5 8 2 9 这样的话我们就可以把一个点的dfs序代表这个点,这样不论树的形状是怎样的,dfs序都可以把它转化成线性结构。
我们通过dfs把这颗树的dfs序存储在pos数组中。
同时用in数组记录dfs过程中访问到该节点的时间戳
out数组记录dfs过程中访问完以该节点为根的子树的时间戳
如此一来,树上某个顶点v在dfs序中的对应区间就是 [ in[v],out[v] ]
题目要求的修改/查询某棵子树就转化成了区间修改 区间查询
故此,我们理所当然地对pos数组建线段树。tree[rt]维护的就是 rt对应的区间[l,r]里面pos[l]、pos[l+1]…pos[r]这些节点的颜色的种数二进制状态。
也就是把颜色压缩成了二进制状态,用线段树维护区间内每个节点的颜色状态并集
,一共60种颜色所以要开long long.
具体的操作可以看代码+注释
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 4e5+7;
const ll inf = 34359738370;
struct node
{
int to,next;
}e[maxn<<1];
int head[maxn],cnt=1;
void add(int from,int to)//前向星建个树
{
e[cnt].next=head[from];
e[cnt].to=to;
head[from]=cnt++;
}
int pos[maxn],tot;//第i个时间点访问到的节点, 时间戳记号
int in[maxn],out[maxn];//入点、出点的时间戳,访问顶点i的时间以及访问完以i为根的树的时间
//[ in[i],out[i] ]就是i对应的dfs序区间 修改这颗树等价于修改这个区间的值 就可以用线段树来维护惹
void dfs(int rt,int par)//求出dfs序存于pos数组 用in out两个时间戳数组进行标记
{
in[rt]=++tot;
pos[tot]=rt;
for(int i=head[rt];i;i=e[i].next)
{
int to=e[i].to;
if(to == par) continue;
dfs(to,rt);
}
out[rt]=tot;
return ;
}
//得到dfs序后 用pos数组建线段树 状态压缩 64位 每位代表特定的颜色
ll tag[maxn<<2],tree[maxn<<2];//tag记录根节点i所代表区间被修改成的颜色
// tree[rt] (对应pos的[l,r]区间)维护pos[l] pos[l+1]....pos[r]这些树的节点构成的集合的颜色种类状态
int c[maxn];//每个顶点的颜色
int n,m;
inline int lc(int rt)
{
return rt<<1;
}
inline int rc(int rt)
{
return rt<<1 | 1;
}
inline void pushup(int rt)
{
tree[rt]=tree[lc(rt)]|tree[rc(rt)];//注意是并集
return ;
}
inline void build(int rt,int l,int r)
{
if(l == r)
{
tree[rt]=1ll<<c[pos[l]];//状态压缩
return ;
}
int mid=(l+r)>>1;
build(lc(rt),l,mid);
build(rc(rt),mid+1,r);
pushup(rt);
return ;
}
inline void pushdown(int rt)
{
if(tag[rt])//tag是0的话就不能pushdown
{
tree[lc(rt)]=tag[rt];
tag[lc(rt)]=tag[rt];
tree[rc(rt)]=tag[rt];
tag[rc(rt)]=tag[rt];
tag[rt]=0;
}
return ;
}
inline void updata(int rt,int l,int r,int vl,int vr,int x)//[vl,vr]区间的值改成x
{
if(r<vl || l>vr) return ;
if(vl<=l && r<=vr)
{
tree[rt]=1ll<<x;//直接覆盖了原先的种类集合
tag[rt]=1ll<<x;//同样直接覆盖
return ;
}
int mid=(l+r)>>1;
pushdown(rt);
updata(lc(rt),l,mid,vl,vr,x);
updata(rc(rt),mid+1,r,vl,vr,x);
pushup(rt);
return ;
}
inline ll query(int rt,int l,int r,int vl,int vr)//查询[vl,vr]内的种类状态
{
if(r<vl || l>vr) return 0;
if(vl<=l && r<=vr)
{
return tree[rt];//先返回一个ll的数 然后再求出这个数二进制下有多少个1即可
}
int mid=(l+r)>>1;
pushdown(rt);
return query(lc(rt),l,mid,vl,vr) | query(rc(rt),mid+1,r,vl,vr);//注意是并集
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",c+i);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d %d",&v,&u);
add(v,u),add(u,v);
}
dfs(1,-1);
build(1,1,n);
while(m--)
{
int f,root,c;
scanf("%d",&f);
if(f == 1)
{
scanf("%d %d",&root,&c);
updata(1,1,n,in[root],out[root],c);
}
else
{
scanf("%d",&root);
ll ans=query(1,1,n,in[root],out[root]);
int k=0;//保存ans二进制下1的个数
while(ans)
{
k+=1&ans;
ans>>=1;
}
printf("%d\n",k);
}
}
return 0;
}