CF620E New Year Tree (dfs序建线段树+状态压缩)

题目链接
在这里插入图片描述

分析:
题目要求是区间修改 区间查询,我们考虑用线段树去搞。
但是这里是在树上,而线段树是维护(线性)区间信息的。

所以我们需要对树做一个预处理,即:求出树的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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值