bzoj4448 [Scoi2015]情报传递(树链剖分+主席树)

17 篇文章 0 订阅
15 篇文章 0 订阅

题目链接

分析:
树上路径问题,显然可以链剖

看完题的我,大脑中浮现的想法↓:
对于每一个修改,在按照树链剖分建立的线段树上单点修改
记录这个点是什么时刻进行的修改
之后在询问的时候,我们只需查找区间内有多少时间戳小于等于“当前时刻-C-1”的结点即可
那么我们就可在线段树里套一个权值平衡树
这道题的题解:树链剖分+线段树+平衡树

我的MA呀,不觉得神烦吗???

实际上我们的问题就出在:查找区间内有多少时间戳小于等于“当前时刻-C-1”的结点

我们可以用主席树完成

我们把所有操作离线
按照在dfs序建立主席树,在主席树上处理完所有的修改操作
(也就说,我们按照dfs,对于每一个结点开一个线段树,而线段树的编号是时间点,如果i的修改时间是x,那么我们就在x点+1)
询问时直接利用树链剖分在主席树中区间查询即可
这样问题就迎刃而解了

tip

自从理解了线段树套平衡树之后,做题就很喜欢往这方面想。。。

在码的时候,突然有点绕不过来了:
主席树肯定建立在dfs序上的权值线段树
但是在询问的时候有时间限制,怎么破?

后来我才发现是我傻。。。
我们在主席树中记录的就是添加修改的时间点
只要查找区间内有多少时间戳小于等于“当前时刻-C-1”的结点即可
和询问出现的时间没有什么直接联系
(间接联系:时间已经体现在“当前时刻-C-1”的这个“当前时刻”中了)

一开始WA了一次
因为我对于没有修改的结点在主席树上没有插入,只有root的继承
后来改成对于没有修改的结点在主席树上插入m,就A了

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=200005;
int n,m,root[N],rt,cntC=0,cntQ=0,sz=0,tt;
int deep[N],top[N],pre[N],num[N],dfn[N],clo=0,size[N],son[N];
struct point{
    int ti,x,y;
};
point C[N],Q[N];
struct po{
    int y,nxt;
};
po way[N];
int st[N],tot=0;
struct node{
    int sum,l,r;
};
node t[N*20];

void add(int u,int w)
{
    tot++;
    way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
}

void dfs_1(int now,int fa,int dep)
{
    deep[now]=dep;
    pre[now]=fa;
    size[now]=1;
    int maxx=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa)
        {
            dfs_1(way[i].y,now,dep+1);
            size[now]+=size[way[i].y];
            if (size[way[i].y]>maxx)
                maxx=size[way[i].y],son[now]=way[i].y;
        }
}

void dfs_2(int now,int fa)
{
    if (son[fa]!=now) top[now]=now;
    else top[now]=top[fa];
    dfn[++clo]=now; num[now]=clo;
    if (son[now])
    {
        dfs_2(son[now],now);
        for (int i=st[now];i;i=way[i].nxt)
            if (way[i].y!=fa&&way[i].y!=son[now])
                dfs_2(way[i].y,now);
    }
}

void insert(int &now,int l,int r,int x)
{
    sz++;
    t[sz]=t[now];
    now=sz;
    t[now].sum++;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) insert(t[now].l,l,mid,x);
    else insert(t[now].r,mid+1,r,x);
}

void ask(int x,int y,int l,int r,int z)
{
    if (l==r) 
    {
        if (l<=z) tt+=t[y].sum-t[x].sum;
        return;
    }
    int mid=(l+r)>>1;
    int tmp=t[t[y].l].sum-t[t[x].l].sum;
    if (z<=mid) ask(t[x].l,t[y].l,l,mid,z);
    else tt+=tmp,ask(t[x].r,t[y].r,mid+1,r,z);
}

int query(int o)
{
    int x=Q[o].x,y=Q[o].y;
    int len=deep[x]+deep[y],ans=0;
    int f1=top[x],f2=top[y];
    while (f1!=f2)
    {
        if (deep[f1]<deep[f2]) swap(f1,f2),swap(x,y);
        tt=0; ask(root[num[f1]-1],root[num[x]],1,m,Q[o].ti);
        ans+=tt;
        x=pre[f1]; f1=top[x];
    }
    if (deep[x]>deep[y]) swap(x,y);
    tt=0; ask(root[num[x]-1],root[num[y]],1,m,Q[o].ti);
    ans+=tt;
    len=len-2*deep[x]+1;

    printf("%d %d\n",len,ans);
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        if (!x) rt=i;
        else add(x,i);
    }

    dfs_1(rt,0,1);
    dfs_2(rt,0);

    scanf("%d",&m);
    int opt,x,y,c;
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&opt);
        if (opt==1) 
        {
            cntQ++;
            scanf("%d%d%d",&Q[cntQ].x,&Q[cntQ].y,&Q[cntQ].ti);
            Q[cntQ].ti=i-Q[cntQ].ti-1;
        } else {
            scanf("%d",&x);
            C[x].ti=i;
        }
    }

    for (int i=1;i<=n;i++)
    {
        root[i]=root[i-1];
        if (C[dfn[i]].ti!=0) insert(root[i],1,m,C[dfn[i]].ti);
        else insert(root[i],1,m,m);   //插入m 
    }

    for (int i=1;i<=cntQ;i++) query(i);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值