线段树合并练习4——luogu P4556 [Vani有约会]雨天的尾巴

蒟蒻的垂死挣扎

千万别去bzoj做,据说爆栈要写bfs。

感谢 @cx233666 提供的毒瘤线段树合并练习题,我原本以为自己短时间内不会再写这个了,他今天跑过来问我这道题,我一看卧槽这不是傻逼树上差分加线段树合并吗,然后一边很爽的码着一边说这题真傻逼。

然后我就成傻逼了。

事实上这题思维难度几乎为零,但是这道题空间只有128MB

只有128MB!!!!!!

 

我什么都不想说了,我洛谷发了个题解,在这里Copy一下。

再次感谢 @cx233666 ,我觉得我的T2可以出一道空间优化题。(俗称卡空间)

 

题解 P4556 【[Vani有约会]雨天的尾巴】

感谢 @cx233666 给我提供的线段树合并毒瘤题

首先楼上的那个代码被某位大佬Hack了,我开始被卡空间来看题解结果发现楼上跟我差不多过了很惊讶,然后一看他线段树合并没有新建节点???(懵逼状态)

我当时以为我学了个假的线段树合并,一看右边,诶被Hack了就很舒服。

反正我是只会新建节点的那种,有谁会不新建的可以来教我。


本题思路 (前置技能 线段树合并)

首先肯定直观的想法就是树上差分然后线段树合并是吧,具体就是对于一条(u,v)的路径,在u和v上放下+1的标记,然后在lca与lca父亲处放上−1的标记,最后一遍dfs用线段树合并统计所有答案(线段树求全局最大值应该会吧)。然后我们就可以直接在根节点处得到答案了。

然后你发现这道题的空间只有苦逼的128MB,对啊,问题就在这里,不然我还讲什么。

我开始像上面那样打了,是最原始的做法,放标记就直接在Dfs之前放完了,而且用的还是新增节点的那种Update,然后一波线段树合并,这样100倍空间好像都过不了。

这样算一波空间复杂度大概是 O(nlogn∗4) 级别的

然后这个是处理放标记的一个数组,开四个总共是100MB左右,然后你还要线段树合并一波,同样是这么大,所以大概要200MB,我只是算了个大概,没仔细分析,但也差不多了。如果有哪位大佬可以有更好的处理空间方式可以告诉我一下,目前这样是完全不行的。

然后我就在网上找标程,结果发现自己很蠢。


线段树合并空间优化

(部分内容仅限拥有某种性质的题目)

首先最基本的一点,因为我们是先修改再合并吧,所以对于已经存在的节点,我们可以直接修改该节点的值,而不是新建节点,这样我们可以优化一部分空间,但是仅仅是常数级别的优化。

但是别着急。

第二点,我们可以打一个垃圾回收(大概是叫这个)。

我们发现在线段树合并之后,并不需要查询某棵树的信息,而对于所有的答案我们可以直接记录下来,所以事实上来说,如果一棵树合并到了它父亲节点上,并且我们记录了这棵树的答案,那么这棵树就没用了,它的空间就可以空出来留给后面用了。

所以我们记一下可以重复利用的点,每次新建节点的时候就优先用这些点,具体代码如下。

    inline int New() { if(tot) return rab[tot--]; return ++cnt; }
    inline void Throw(int x) { rab[++top]=x,ls[x]=rs[x]=Max[x]=id[x]=0; }

rab数组是垃圾数组,也就是记录所有可以重复利用的点。

tot是垃圾数 cnt是所有节点的个数,也就是新建全新的节点。

New 函数在你需要新建节点的时候用,返回节点编号。

Throw 意味着这个节点没用了,你可以直接丢掉它。

这样我们就可以极大的提高空间的利用率。

但是你发现单纯加这个依然过不了。

(如果你是Dfs之前加标记的话)

这样我们看起来优化了大概一半的空间,看起来是过了的,实际上,是假的,看起来大概是合并时保留的节点数只有当前几个线段树?再想想,不只是这些,所有询问的空间还在里面,你会带着这些空间一起跑,而这样的空间并不是一层,而是所有的空间慢慢减小,最后只剩下根节点那棵树的空间。

所以我们考虑边Dfs边放标记,当然这里指把标记丢进线段树里,不过如果你是将儿子合并向自己的话,依旧是假的,菊花树大概就凉了,所以这里我们要把自己合并向父亲,这样我们每次就是用到了当前这个点的线段树和正在被合并的父亲的那棵线段树,这样的复杂度最大就是两倍的nlogn吧,而实际上取决于信息的密度,实际比这个好像要小很多,本题数据开40倍空间是完全足够的,网上看见一个二十倍空间的写Bfs的过了,就很强。

关于空间问题,如果有更加详细的证明可以教我一下,本人并不是特别能够理解这个。本文仅当抛砖引玉,或是给予为空间问题而烦恼的选手指引道路。

下面是通过代码。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<ctime>
using namespace std;
#define ll long long
#define N 101000
#define Z 100000
#define inf 1e9
#define RG register
inline ll read(){
    RG ll x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}

int n,m,top,first[N],ans[N];
struct mona { int nxt,en; } s[N<<1];
struct joker { int nxt,en,op; } ;
inline void Insert(int x,int y) { s[++top]=(mona) { first[x],y },first[x]=top; }
struct SeqTree{
    int cnt,ls[N*40],rs[N*40],Max[N*40],id[N*40],rt[N],rab[N*40],tot,top,first[N]; joker s[N<<2]; 
    inline void Insert(int x,int y,int op) { s[++top]=(joker) { first[x],y,op },first[x]=top; }
    inline int New() { if(tot) return rab[tot--]; return ++cnt; }
    inline void Throw(int x) { rab[++top]=x,ls[x]=rs[x]=Max[x]=id[x]=0; }
    inline void Pushup(int x){
        if(Max[ls[x]]>=Max[rs[x]]) Max[x]=Max[ls[x]],id[x]=id[ls[x]];
        else Max[x]=Max[rs[x]],id[x]=id[rs[x]];
    }
    inline void Modify(int l,int r,int &x,int pos,int val){
        if(!x) x=New(); if(l==r) { Max[x]+=val,id[x]=l;  }
        else{int mid=l+r>>1;
        if(pos<=mid) Modify(l,mid,ls[x],pos,val);
        else Modify(mid+1,r,rs[x],pos,val);
        Pushup(x);} if(!Max[x]) id[x]=0;
    }
    inline int Merge(int l,int r,int u,int v){
        if(!u||!v) return u|v;
        int now=New(),mid=l+r>>1; if(l==r) Max[now]=Max[u]+Max[v],id[now]=l;
        else ls[now]=Merge(l,mid,ls[u],ls[v]),rs[now]=Merge(mid+1,r,rs[u],rs[v]),Pushup(now);
        Throw(u),Throw(v); return now;
    }
} T;

struct Tree{
    int top[N],son[N],siz[N],fa[N],dep[N];
    inline void Dfs(int k,int Fa){
        fa[k]=Fa,siz[k]=1,dep[k]=dep[Fa]+1;
        for(RG int i=first[k];i;i=s[i].nxt){
            int en=s[i].en; if(en==Fa) continue ;
            Dfs(en,k),siz[k]+=siz[en];
            if(siz[son[k]]<siz[en]) son[k]=en;
        }   return ;
    }
    inline void Dfs2(int k,int to){
        top[k]=to; if(son[k]) Dfs2(son[k],to);
        for(RG int i=first[k];i;i=s[i].nxt){
            int en=s[i].en; if(en==fa[k]||en==son[k]) continue ;
            Dfs2(en,en);
        }
    }
    inline int Lca(int u,int v){
        while(top[u]!=top[v]){
            if(dep[top[u]]<dep[top[v]]) swap(u,v);
            u=fa[top[u]];
        }   return dep[u]<dep[v]?u:v;
    }
} S;

inline void Dfs(int k,int fa){
    for(RG int i=first[k];i;i=s[i].nxt) if(s[i].en!=fa) Dfs(s[i].en,k);
    for(RG int i=T.first[k];i;i=T.s[i].nxt) T.Modify(1,Z,T.rt[k],T.s[i].en,T.s[i].op);
    ans[k]=T.id[T.rt[k]];
    if(S.fa[k]) T.rt[S.fa[k]]=T.Merge(1,Z,T.rt[S.fa[k]],T.rt[k]);
    return ;
}

int main(){
    n=read(),m=read();
    for(RG int i=1;i<n;++i){
        int x=read(),y=read();
        Insert(x,y),Insert(y,x);
    }   S.Dfs(1,0),S.Dfs2(1,1);
    for(RG int i=1;i<=m;++i){
        int u=read(),v=read(),val=read();
        int lca=S.Lca(u,v),Flca=S.fa[lca];
        T.Insert(u,val,1),T.Insert(v,val,1),T.Insert(lca,val,-1),T.Insert(Flca,val,-1);
    }   Dfs(1,0); for(RG int i=1;i<=n;++i) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值