uoj#295. 【ZJOI2017】线段树(树上倍增)

20 篇文章 0 订阅
6 篇文章 0 订阅

先放代码,日后更。

============================2018.3.21UPD============================
题面在这里

做法

首先需要了解zkw线段树的操作过程。(不懂的百度一下)
大概就是从两个叶节点开始,维护两个指针,一个指向 l l 左边一位,一个指向r右边一位,不停向上跳。然后模仿这个过程同样在这个广义线段树上操作,画一下图可以发现,假设 l1 l − 1 所在叶节点 u u r+1所在叶节点 v v ,一个线段[l,r]会分成的若干个小线段恰好是 ulca(u,v) u − l c a ( u , v ) 链上所有左儿子的右兄弟,以及 vlca(u,v) v − l c a ( u , v ) 链上所有右儿子的左兄弟。
一定要画图!!!多观察!!!
发现这一点后就很容易了。将一条链按 lca l c a 分成两半,然后大力分类讨论,树上倍增处理一下,细节超多。比如, l=1 l = 1 r=n r = n 的情况需要特殊处理,还有 u u <script type="math/tex" id="MathJax-Element-156">u</script>的位置比较特殊的时候也需要判一下。
建议自己推推看qwq。(我当时都是自己想的qwq,感觉想出一道zjoi题自己萌!萌!哒!

代码

/*
*   zkw线段树的科技;
*   一堆细节;
*   考试一定要对拍!不对拍会死的!
*/
#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x); i<=(y); i++)
#define per(i,x,y) for (int i=(x); i>=(y); i--)
#define N 400010
#define ll long long
using namespace std;
ll read(){
    char ch=getchar(); ll x=0; int op=1;
    for (; !isdigit(ch); ch=getchar()) if (ch=='-') op=-1;
    for (; isdigit(ch); ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*op;
}
int n,m,tot,rt,now,clk,a[N],f[N][20],pos[N],in[N],out[N];
ll g[N],h[N],f1s[N],f2s[N],gs[N],hs[N],ans,dep[N];
struct seg{
    int l,r,ls,rs,mid;
    seg(){ l=r=ls=rs=mid=0; }
}tr[N];
void build(int &o,int l,int r,int d,int fa,bool fl){
    o=++tot; in[o]=++clk; tr[o].l=l; tr[o].r=r; dep[o]=d; f[o][0]=fa;
    if (!fl){//左儿子
        g[o]=g[fa]+d; gs[o]=gs[fa]+1;
        h[o]=h[fa]; hs[o]=hs[fa];
        f1s[o]=f1s[fa]+d-1; f2s[o]=f2s[fa];
    } else{
        g[o]=g[fa]; gs[o]=gs[fa];
        h[o]=h[fa]+d; hs[o]=hs[fa]+1;
        f1s[o]=f1s[fa]; f2s[o]=f2s[fa]+d-1;
    }
    if (l==r){ pos[l]=o; out[o]=++clk; return; } int k=a[now]; tr[o].mid=k;
    if (l<k) now++; build(tr[o].ls,l,k,d+1,o,0);
    if (k+1<r) now++; build(tr[o].rs,k+1,r,d+1,o,1);
    out[o]=++clk;
}
void print_tree(int u){
    printf("id: %d, l=%d, r=%d\n",u,tr[u].l,tr[u].r);
    if (tr[u].ls) print_tree(tr[u].ls);
    if (tr[u].rs) print_tree(tr[u].rs);
}
int lca(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);
    int tmp=dep[x]-dep[y];
    per (i,18,0) if (tmp>>i&1) x=f[x][i];
    if (x==y) return x;
    per (i,18,0) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
bool isanc(int x,int y){ return in[x]<=in[y] && out[x]>=out[y]; }
int main(){
    /*freopen("B.in","r",stdin);
    freopen("B.out","w",stdout);*/
    n=read();
    rep (i,1,n-1) a[i]=read();
    m=read();
    now=1; build(rt,1,n,0,0,1);
    rep (j,1,18) rep (i,1,tot) f[i][j]=f[f[i][j-1]][j-1];
    while (m--){
        int u=read(),l=read(),r=read(),x,y,z,w,v,tmp;
        ans=0;
        if (l==1 && r==n){ printf("%d\n",dep[u]); continue; }
        x=pos[l-1]; y=pos[r+1]; tmp=lca(x,y); z=tr[tmp].ls; if (l==1 || r==1) z=1;
        if (l!=1){
            w=lca(x,u);
            if (isanc(w,z)){
                ll sum=gs[x]-gs[z];
                ans+=g[x]-g[z]+sum*dep[u]-2ll*sum*dep[w];
                if (w==z && isanc(tr[w].rs,u)) ans-=2;
            } else{
                ll sum=gs[x]-gs[w];
                ans+=g[x]-g[w]+sum*dep[u]-2ll*sum*dep[w];
                sum=gs[w]-gs[z];
                ans+=g[w]-g[z]+sum*dep[u]-2ll*(f1s[w]-f1s[z]);
                if (isanc(tr[w].rs,u)) ans-=2;
            }
        }
        z=tr[tmp].rs; if (l==1 || r==1) z=1;
        if (r!=n){
            w=lca(y,u);
            if (isanc(w,z)){
                ll sum=hs[y]-hs[z];
                ans+=h[y]-h[z]+sum*dep[u]-2ll*sum*dep[w];
                if (w==z && isanc(tr[w].ls,u)) ans-=2;
            } else{
                ll sum=hs[y]-hs[w];
                ans+=h[y]-h[w]+sum*dep[u]-2ll*sum*dep[w];
                sum=hs[w]-hs[z];
                ans+=h[w]-h[z]+sum*dep[u]-2ll*(f2s[w]-f2s[z]);
                if (isanc(tr[w].ls,u)) ans-=2;
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值