题目分析

处理广义线段树的一类套路方法。

首先,定义原来的线段树为原树,并且将其改造一下,使得它能够管理的区间为 $[0,n+1]$。

定义左偏树(跟一种可并堆重名了 2333)为一棵将原树上,所有是左儿子的点提取出来,构成的一棵树,每个点的父亲,是代表在其左边,与其代表区间相邻的区间,且深度比它浅的节点。画出来是这样的:

我想我需要一块手绘板

定义右偏树为提取右儿子们,与在它右边深度比它浅的第一个节点相连的树。

左偏树可以在遍历原树时,开个栈,每次先将当前点的左儿子与栈顶元素相连,然后将当前点左儿子压入栈,遍历当前点右子树,再将左儿子弹出栈,再遍历左子树,如此得到。右偏树类似。

接下来,对于一个询问 $(x,l,r)$,首先找到代表区间 $[l-1,l-1]$的点 $k_1$和代表区间 $[r+1,r+1]$的点 $k_2$(这也就是要改造原树使它能够控制区间 $[0,n+1]$的原因),然后求出它们的 LCA,点 $o$。

不难发现,$k_1$到 $o$的路径上经过的所有点,若它的右儿子没被经过,则右儿子在 $S_{[l,r]}$中,并且这些点还对应了一条右偏树上的链。从 $k_2$到 $o$路径上,那些点的左儿子也有类似的性质。

若 $k_1$为一个左儿子,则这条右偏树上链最深点为它的兄弟。若它是右儿子,则最深点为其在右偏树上的父亲。而最浅点的父亲为 $o$的右儿子。($k_2$部分类似,略过)

若 dfs 一次右偏树,每进入一个点 $x$,序列末尾加一个元素 $+x$,表示处理序列一个前缀处理到此,要将 $x$加入集合。离开 $x$时,加入 $-x$,表示要将 $x$从集合中 ban 掉。那么一个询问在右偏树上构成的那条链,就是最深点的正位置的前缀操作一遍剩下的集合的贡献,减去最浅点父亲正位置的集合的前缀贡献。

于是我们将询问离线,顺序处理,每次维护一个前缀的贡献,然后再加加减减就可以得到答案了。

而询问 $(x,l,r)$要问的东西等价于问

$$dep_x|S_{[l,r]}|+\sum_{y \in S_{[l,r]}} dep_y-2\sum_{y \in S_{[l,r]}} dep_{lca(x,y)}$$

前两项只要维护当前集合的元素个数和元素 $dep$和即可,很简单,后一项呢?

也不难,每将一个 $y$加入集合,就在所有 $y$的祖先处把一个标记+1,ban 掉时-1,则 $x$的所有祖先的标记和,就是 lca 的深度和。可以用树链剖分+线段树维护。

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
typedef long long LL;
const int N=400010;
int n,m,SZ_A,rt_A,st_top,js1,js2,tim;
int son[N][2],fa[N],st[N],pos[N],dep[N],sz[N],top[N],dfn[N];LL ans[N];
struct node{int p,x,v,id;}q1[N<<1],q2[N<<1];
bool cmp(node A,node B) {return A.p<B.p;}

struct segment_tree{
    LL sum[N<<2],lz[N<<2];
    void build(int s,int t,int i) {
        sum[i]=lz[i]=0;if(s==t) return;
        int mid=(s+t)>>1;
        build(s,mid,i<<1),build(mid+1,t,(i<<1)|1);
    }
    void put_lz(int s,int t,int i,int v) {lz[i]+=v,sum[i]+=1LL*(t-s+1)*v;}
    void pd(int s,int t,int i) {
        int mid=(s+t)>>1;
        put_lz(s,mid,i<<1,lz[i]),put_lz(mid+1,t,(i<<1)|1,lz[i]),lz[i]=0;
    }
    void modify(int l,int r,int s,int t,int i,int v) {
        if(l<=s&&t<=r) {put_lz(s,t,i,v);return;}
        int mid=(s+t)>>1;if(lz[i]) pd(s,t,i);
        if(l<=mid) modify(l,r,s,mid,i<<1,v);
        if(mid+1<=r) modify(l,r,mid+1,t,(i<<1)|1,v);
        sum[i]=sum[i<<1]+sum[(i<<1)|1];
    }
    LL query(int l,int r,int s,int t,int i) {
        if(l<=s&&t<=r) return sum[i];
        int mid=(s+t)>>1;LL re=0;if(lz[i]) pd(s,t,i);
        if(l<=mid) re=query(l,r,s,mid,i<<1);
        if(mid+1<=r) re+=query(l,r,mid+1,t,(i<<1)|1);
        return re;
    }
}ST;
void walk(int x,int v)
    {while(x) ST.modify(dfn[top[x]],dfn[x],1,SZ_A,1,v),x=fa[top[x]];}
LL q_walk(int x) {
    LL re=0;
    while(x) re+=ST.query(dfn[top[x]],dfn[x],1,SZ_A,1),x=fa[top[x]];
    return re;
}

struct Tree{
    int h[N],ne[N],to[N],p[N],pos[N],fa[N],tot,js;
    void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
    void build(int x,int o) {
        if(!son[x][0]&&!son[x][1]) return;
        fa[son[x][o]]=st[st_top],add(st[st_top],son[x][o]),st[++st_top]=son[x][o];
        build(son[x][o^1],o),--st_top,build(son[x][o],o);
    }
    void dfs(int x) {
        p[++js]=x,pos[x]=js;
        for(RI i=h[x];i;i=ne[i]) dfs(to[i]);
        p[++js]=-x;
    }
    void work(node *q,int qjs) {
        sort(q+1,q+1+qjs,cmp),ST.build(1,SZ_A,1);
        LL now_sumd=0,now_sum=0;
        for(RI i=1,j=1;i<=js;++i) {
            if(p[i]<0) now_sumd-=dep[-p[i]],--now_sum,walk(-p[i],-1);
            else now_sumd+=dep[p[i]],++now_sum,walk(p[i],1);
            while(j<=qjs&&q[j].p==i) {
                LL tmp=now_sumd+now_sum*(LL)dep[q[j].x]-2*q_walk(q[j].x);
                ans[q[j].id]+=q[j].v*tmp,++j;
            }
        }
    }
}lT,rT;

void build_A(int &x,int s,int t) {
    x=++SZ_A;if(s==t) {pos[s]=x;return;}
    int mid=read();
    build_A(son[x][0],s,mid),build_A(son[x][1],mid+1,t);
}
void dfs1(int x,int las) {
    sz[x]=1,fa[x]=las,dep[x]=dep[las]+1;
    if(son[x][0]) dfs1(son[x][0],x),sz[x]+=sz[son[x][0]];
    if(son[x][1]) dfs1(son[x][1],x),sz[x]+=sz[son[x][1]];
}
void dfs2(int x) {
    dfn[x]=++tim;if(!son[x][0]&&!son[x][1]) return;
    if(sz[son[x][0]]>sz[son[x][1]]) {
        top[son[x][0]]=top[x],dfs2(son[x][0]);
        top[son[x][1]]=son[x][1],dfs2(son[x][1]);
    }
    else {
        top[son[x][1]]=top[x],dfs2(son[x][1]);
        top[son[x][0]]=son[x][0],dfs2(son[x][0]);
    }
}
int lca(int x,int y) {
    while(top[x]!=top[y]) dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]];
    return dep[x]<dep[y]?x:y;
}
int is(int x) {return son[fa[x]][1]==x;}

int main()
{
    n=read();
    build_A(rt_A,1,n);
    ++SZ_A,son[SZ_A][0]=rt_A;
    ++SZ_A,son[SZ_A-1][1]=SZ_A,pos[n+1]=SZ_A;
    ++SZ_A,son[SZ_A][1]=SZ_A-2,rt_A=SZ_A;
    ++SZ_A,son[SZ_A-1][0]=SZ_A,pos[0]=SZ_A;
    dfs1(rt_A,0),top[rt_A]=rt_A,dfs2(rt_A);
    st[++st_top]=rt_A,lT.build(rt_A,0),rT.build(rt_A,1);
    lT.dfs(rt_A),rT.dfs(rt_A);
    m=read();
    for(RI i=1;i<=m;++i) {
        int x=read(),l=read(),r=read();
        int k1=pos[l-1],k2=pos[r+1],o=lca(k1,k2);
        q1[++js1]=(node){lT.pos[son[o][0]],x,-1,i};
        if(is(k2)) q1[++js1]=(node){lT.pos[son[fa[k2]][0]],x,1,i};
        else q1[++js1]=(node){lT.pos[lT.fa[k2]],x,1,i};
        q2[++js2]=(node){rT.pos[son[o][1]],x,-1,i};
        if(is(k1)) q2[++js2]=(node){rT.pos[rT.fa[k1]],x,1,i};
        else q2[++js2]=(node){rT.pos[son[fa[k1]][1]],x,1,i};
    }
    lT.work(q1,js1),rT.work(q2,js2);
    for(RI i=1;i<=m;++i) printf("%lld\n",ans[i]);
    return 0;
}
分类: 文章

litble

苟...苟活者在淡红的血色中,会依稀看见微茫的希望

6 条评论

Remmina · 2019年6月19日 10:43 下午

哇你们学了好多新的算法呀 _(:з」∠)_
刚考完中考的我感觉数学挂了 QAQ
怕不是要留级了 QAQ

    Rayment · 2019年6月22日 3:18 下午

    (中考完了有时间修锅吗) 不知道为啥好像发不了文章啊 OAO

      Remmina · 2019年6月22日 10:11 下午

      不存在的吧?不然 litble 怎么发出来的哇?

        litble · 2019年6月22日 10:12 下午

        您康康 CGZ 有没有作者权限吧

        Rayment · 2019年6月23日 7:25 上午

        可能是我没权限吧

          Remmina · 2019年6月24日 12:02 上午

          很抱歉啊 QAQ 之前调整权限的时候把 yzoier 分组里的同学们全部调成了 “订阅者”(然而应该改成 “投稿者” 的),很抱歉 QAQ 现已将部分同学的权限恢复为 “作者”,如有遗漏请及时联系 Remmina T^T

发表评论

电子邮件地址不会被公开。 必填项已用*标注