LGP11306 [COTS 2016] 搜索树 Jelka 学习笔记

LGP11306 [COTS 2016] 搜索树 Jelka 学习笔记

Luogu Link

题意简述

给定一棵 n 个结点、以 1 为根的二叉树。每个点有一个点权。有 m 次对点权的单点修改,求出每次修改后这棵树上有多少结点的子树是二叉搜索树。

n,m2×105

做法解析

题目问的判定为“是否是二叉搜索树”。我们拆解一下这个判定的性质。

BST具有子结构性质,也就是说如果孩子的子树不是BST,那自己也肯定不是。
我们维护每个结点子树内的最大值和最小值。如果对于结点 u 满足 lsursu 的子树均为BST且 mxlsuvumnrsu,那么 u 的子树就是BST。
一开始,我们做一遍DFS从底向上判断答案;对于每次修改,我们需要修改一条链上面的值。然后二分 u1 的链上合法与非法的分界处。时间复杂度 O(Nlog2N)

但是懒得写线段树,怎么办呢?

BST具有性质:中序遍历得到的值序列单调不降。另外,一个子树的DFS序肯定是连续的。问题就变成了对于每个结点 u,存不存在 dfnui<i+1<dfnu+sizuAi<Ai+1,其中 Adfnu=vu。现在问题就变成了每次对 A 单点改,然后树上倍增二分,树状数组区间查 A。虽然还是两只 log,但是常熟小,写起来更简单。

代码实现

#include <bits/stdc++.h>
using namespace std;
namespace obasic{
    template <typename _T>
    void readi(_T &x){
        _T k=1;x=0;char ch=getchar();
        for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
        for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
        x*=k;return;
    }
    template <typename _T>
    void writi(_T x){
        if(x<0)putchar('-'),x=-x;
        if(x>9)writi(x/10);
        putchar(x%10+'0');
    }
};
using namespace obasic;
const int MaxN=2e5+5,Inf=0x3f3f3f3f;
int N,M,X,Y,A[MaxN],B[MaxN],dfn[MaxN],dcnt,ans;
int ch[MaxN][2],tfa[MaxN][20],ld[MaxN],rd[MaxN];
void dfs(int u,int f){
    tfa[u][0]=f;
    for(int i=1;i<20;i++){
        if(!tfa[u][i-1])break;
        tfa[u][i]=tfa[tfa[u][i-1]][i-1];
    }
    ld[u]=dcnt+1;
    if(ch[u][0])dfs(ch[u][0],u);
    dfn[u]=++dcnt;
    if(ch[u][1])dfs(ch[u][1],u);
    rd[u]=dcnt;
}
struct BinidTree{
    int n,t[MaxN];
    void init(int x){n=x,fill(t,t+n+1,0);}
    int lowbit(int x){return x&(-x);}
    void add(int p,int x){for(;p<=n;p+=lowbit(p))t[p]+=x;}
    int gts(int p){int res=0;for(;p;res+=t[p],p-=lowbit(p));return res;}
    int getsum(int l,int r){return gts(r)-gts(l-1);}
}BiT;
int solve(int u){
    if(BiT.getsum(ld[u]+1,rd[u]))return 0;
    int res=0;for(int i=19;~i;i--){
        int a=tfa[u][i];if(!a)continue;
        if(!BiT.getsum(ld[a]+1,rd[a]))res+=(1<<i),u=a;
    }
    return res+1;
}
int main(){
    readi(N),readi(M);BiT.init(N);
    for(int i=1;i<=N;i++)readi(ch[i][0]),readi(ch[i][1]);
    for(int i=1;i<=N;i++)readi(B[i]);
    dfs(1,0);
    for(int i=1;i<=N;i++)A[dfn[i]]=B[i];
    for(int i=2;i<=N;i++)if(A[i]<A[i-1])BiT.add(i,1);
    for(int i=1;i<=N;i++)ans+=(!BiT.getsum(ld[i]+1,rd[i]));
    A[N+1]=Inf;
    for(int i=1,du;i<=M;i++){
        readi(X),readi(Y);
        ans-=solve(X),du=dfn[X];
        if(A[du-1]>A[du])BiT.add(du,-1);
        if(A[du+1]<A[du])BiT.add(du+1,-1);
        A[du]=Y;
        if(A[du-1]>A[du])BiT.add(du,1);
        if(A[du+1]<A[du])BiT.add(du+1,1);
        ans+=solve(X);
        writi(ans),puts("");
    }
    return 0;
}

反思总结

思考子结构性质,这决定能不能二分判定。
观察每次修改的影响范围是否只是单点到根的一条链。
BST中序遍历单调不降。
维护“是否有”这样的信息时,考虑从维护信息本身变成维护“是否存在”的01值。
单老葛做法还不会\ll

posted @   矞龙OrinLoong  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示