题目链接
题意:给定一棵树,给你两个操作,一是把v和它的子树的所有数变成x,二是查询v和它的子树中不同的数有几个。
思路:很妙的解法,由于值不超过60,所有之间可以用二进制或操作来判断不同的数的个数,比如2和3,我就变成100和1000,相或后就变成1100,只要查看最后的结果有几个1就是不同的数的个数,至于子树的话用线段树+dfs序就可以,线段树pushup的时候相或就可以了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e5+1;
vector<int>g[maxn];
int tot=0,num[maxn],b[maxn],in[maxn],out[maxn];
struct cxk{
int l,r;
ll lazy,sum;
}tree[maxn<<2];
void dfs(int u,int fa)
{
in[u]=++tot;
b[tot]=u;
for(int to:g[u])
{
if(to==fa) continue;
dfs(to,u);
}
out[u]=tot;
}
void pushup(int x)
{
tree[x].sum=tree[x<<1].sum|tree[x<<1|1].sum;
}
void pushdown(int x)
{
if(tree[x].lazy)
{
tree[x<<1].lazy=tree[x<<1|1].lazy=tree[x].lazy;
tree[x<<1].sum=(1LL<<tree[x].lazy);
tree[x<<1|1].sum=(1LL<<tree[x].lazy);
tree[x].lazy=0;
}
}
void build(int x,int l,int r)
{
tree[x].l=l;tree[x].r=r;
if(l==r){
tree[x].sum=(1LL<<num[b[l]]);
return ;
}
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
pushup(x);
}
void update(int x,int l,int r,int v)
{
if(l<=tree[x].l&&tree[x].r<=r) {
tree[x].sum=(1LL<<v);
tree[x].lazy=v;
return ;
}
pushdown(x);
int mid=(tree[x].l+tree[x].r)>>1;
if(l<=mid) update(x<<1,l,r,v);
if(mid<r) update(x<<1|1,l,r,v);
pushup(x);
}
ll query(int x,int l,int r)
{
if(l<=tree[x].l&&tree[x].r<=r) return tree[x].sum;
pushdown(x);
int mid=(tree[x].l+tree[x].r)>>1;
ll ans=0;
if(l<=mid) ans|=query(x<<1,l,r);
if(mid<r) ans|=query(x<<1|1,l,r);
pushup(x);
return ans;
}
int main()
{
int n,m,u,v,op,x,y;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&num[i]);
for(int i=1;i<n;++i)
{
scanf("%d %d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
build(1,1,n);
for(int i=1;i<=m;++i)
{
scanf("%d",&op);
if(op==1) scanf("%d %d",&x,&y),update(1,in[x],out[x],y);
else{
scanf("%d",&x);
ll ans=query(1,in[x],out[x]),cnt=0;
for(ll j=1;j<=61;++j)
if((ans&(1LL<<j))) cnt++;
printf("%lld\n",cnt);
}
}
}