【bzoj 4771】 七彩树

Description

给定一棵树,有n个点,点有颜色,颜色范围[1,n]。
现有m个询问,形如x d
表示询问点x子树内与x深度差不超过d的点集中,不同的颜色种类数
强制在线
要求做到nlogn
1<=n<=100000 1<=m<=100000

离线

离线的话方法很多,其中一种是线段树合并。
开一棵线段树,下标是颜色,若该颜色出现过,那么值是最浅深度
再开一棵线段树,下表是深度,值是子树内深度为x的不同颜色数
我们用颜色线段树来辅助深度线段树,后者用来计算各个点处的答案。颜色线段树合并的时候,如果发现颜色重复,就在深度线段树中的max(depmin[x],depmin[y])处减重

在线

将上面的线段树合并过程中的线段树可持久化即可支持在线询问
至于空间复杂度,其实还是 O(nlogn) 的,因为当线段树合并的时候,只有两棵线段树共有的结点需要新建,而合并过程的共有结点总数就是 O(nlogn) ,那么最后可持久化新建节点的总数也是 O(nlogn)

dfs序

这是基于dfs序的另一种做法,考虑到是子树问题一个显然的思路是往dfs序方向上去想
先不考虑深度限制,可以用主席树来做(相当于二维,一维dfs序区间一维颜色)
现在有了深度限制,题目显然不允许再多套一维了(深度)
考虑如何消掉一维
当没有深度限制的时候,将同种颜色的点拉出来按照dfn排序。每个点对应位置+1,两两相邻lca处-1。这样显然是对的,子树查询就是dfs序上区间求和,只需要一维的线段树即可
那么再用函数式数据结构多套上一维深度就能支持询问了
复杂度也是 O(nlogn)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,b,a) for(int i=b;i>=a;--i)
#define efo(i,v,u) for(int i=BB[v],u=B[BB[v]][1];i;i=B[i][0],u=B[i][1])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
char ch;
void read(int &n){n=0;int p=1;for(ch=getchar();ch<'0' || ch>'9';ch=getchar())if(ch=='-') p=-1;for(;'0'<=ch && ch<='9';ch=getchar()) n=n*10+ch-'0';n*=p;}
const int N=1e5+5;
int n,c[N],fa[N],B0,BB[N],B[N][2],dep[N];
void link(int u,int v){B[++B0][1]=v,B[B0][0]=BB[u],BB[u]=B0;}
int p1,rt1[N],ls1[N*20],rs1[N*20],dmn[N*20];//color dep
int p2,rtdel,rt2[N],ls2[N*80],rs2[N*80],sm[N*80];//dep ans
int ins1(int l,int r,int x,int d)
{
    int v=++p1;
    if(l==r){dmn[v]=d;return v;}
    int mid=l+r>>1;
    if(x<=mid) ls1[v]=ins1(l,mid,x,d);
    else rs1[v]=ins1(mid+1,r,x,d);
    return v;
}
int ins2(int l,int r,int x,int y)
{
    int v=++p2;
    sm[v]=y;
    if(l==r) return v;
    int mid=l+r>>1;
    if(x<=mid) ls2[v]=ins2(l,mid,x,y);
    else rs2[v]=ins2(mid+1,r,x,y);
    return v;
}
int merge2(int x,int y)
{
    if(!x || !y) return x+y;
    int v=++p2;
    if(!ls2[x] && !rs2[x])//leaf
    {
        sm[v]=sm[x]+sm[y];
        return v;
    }
    ls2[v]=merge2(ls2[x],ls2[y]);
    rs2[v]=merge2(rs2[x],rs2[y]);
    sm[v]=sm[ls2[v]]+sm[rs2[v]];
    return v;
}
int merge1(int x,int y)
{
    if(!x || !y) return x+y;
    if(!ls1[x] && !rs1[x])//leaf
    {
        rtdel=merge2(rtdel,ins2(1,n,max(dmn[x],dmn[y]),-1));
        dmn[x]=min(dmn[x],dmn[y]);
        return x;
    }
    ls1[x]=merge1(ls1[x],ls1[y]);
    rs1[x]=merge1(rs1[x],rs1[y]);
    return x;
}
void dfs(int v)
{
    rt1[v]=ins1(1,n,c[v],dep[v]);
    rt2[v]=ins2(1,n,dep[v],1);
    efo(i,v,u)
    {
        dep[u]=dep[v]+1;
        dfs(u);
    }
    rtdel=0;
    efo(i,v,u)
    {
        rt1[v]=merge1(rt1[v],rt1[u]);
        rt2[v]=merge2(rt2[v],rt2[u]);
    }
    rt2[v]=merge2(rt2[v],rtdel);
}
int query(int v,int l,int r,int x,int y)
{
    if(l==x && r==y) return sm[v];
    int mid=l+r>>1;
    if(y<=mid) return query(ls2[v],l,mid,x,y);
    else
    if(x>mid) return query(rs2[v],mid+1,r,x,y);
    else
    return query(ls2[v],l,mid,x,mid)+query(rs2[v],mid+1,r,mid+1,y);
}
int main()
{
    freopen("bzoj4771.in","r",stdin);
    freopen("bzoj4771.out","w",stdout);
    int cas,x,y,Q,d;
    for(read(cas);cas;cas--)
    {
        mset(dmn,127);mset(rt1,0);mset(rt2,0);
        B0=0;mset(BB,0);
        fo(i,1,p1) ls1[i]=rs1[i]=0;p1=0;
        fo(i,1,p2) ls2[i]=rs2[i]=sm[i]=0;p2=0;
        read(n),read(Q);
        fo(i,1,n) read(c[i]);
        fo(i,2,n) read(fa[i]),link(fa[i],i);
        dep[1]=1;dfs(1);
        int ans=0;
        while(Q--)
        {
            read(x),read(d);x^=ans,d^=ans;
            printf("%d\n",ans=query(rt2[x],1,n,dep[x],min(n,dep[x]+d)));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值