线段树之单侧递归

有的时候我们pushup不能直接由左,右儿子存储的信息来更新自己的信息,这时我们就需要去某个儿子的子树中去找信息。因此得名。

其实这个并非很难。让我们来一道例题。

楼房重建

说人话:单点修改+求序列前缀最大值(即前面的数都比他小)个数。

我们考虑用线段树来维护 \(p\) 点所对应的区间内的前缀最大值个数 \(ans_p\)。但是我们会发现一个问题,在合并左右儿子答案的时候,可能会出现如下情况。(标红的即为区间内的前缀最大值)

然后我们发现,如果只把两个区间的前缀最大值个数相加的话,这个答案是错的呀!因为右儿子的前缀最大值可能不是原序列的前缀最大值,比如说右子的第一个数,被左子的最后一个前缀最大值偏序了。

那我们就需要到右子里面找,\(>\) 给定值 \(x\) 的前缀最大值个数。我们容易想到下面的实现。下面 mx 表示的是区间最大值。

#define lc (p<<1)
#define rc (p<<1|1)
int qry(long double x,int s,int t,int p)
{
    if(mx[p]<=x)return 0;//这个区间所有数都小于等于 x,>x的前缀最大值个数=0
    if(s==t)return 1;
    int mid=(s+t)>>1;
    if(mx[lc]<=x)return qry(x,mid+1,t,rc);//如果说左边的所有数都小于等于 x,左边肯定没贡献,只可能在右边
    //否则说明,左子节点内存在大于x的数了,我们要返回的是qry(x,s,mid,lc)+qry(mx[lc],mid+1,t,rc)
    //然后a[p]=a[lc]+qry(mx[lc],mid+1,t,rc),所以我们只用单侧递归即可
    return a[p]-a[lc]+qry(x,s,mid,lc);
}
void pup(int s,int t,int p)
{
    int mid=(s+t)>>1;
    a[p]=a[lc]+qry(mx[lc],mid+1,t,rc);
    mx[p]=max(mx[lc],mx[rc]);
}

单次 pup 复杂度 \(\log n\),单次修改调用 \(\log n\)pup,总时间复杂度 \(O(m\log^2 n)\),可以通过本题。

是不是非常简单?

Distinct Integers

首先,我们记 \(pre_i\) 表示与 \(a_i\) 相同的上一个数的位置。如样例的 0 1 2 1 4 对应的 \(pre\) 序列为 -1 -1 -1 1 -1。注意题目给定的下标从 \(0\) 开始。

注意到,如果是全局查询的话,则以 \(i\) 位置为右端点的区间共有 \(i-\max_{j=1}^i pre_j\) 个。这启示我们,如果能算出 \(pre\) 数组前缀 \(\max\) 的区间和的话即可解决这道题目。

感觉和上一题的处理办法几乎一模一样。

code
#include<cstdio>
#include<algorithm>
#include<utility>
#include<set>
#define int long long
#define lc (p<<1)
#define rc (p<<1|1)
#define mid ((s+t)>>1)
using namespace std;
typedef pair<int,int> pii;
const int mn=5e5+5;
int n,q,a[mn],lst[mn];
struct SEG
{
    int a[mn<<2],mx[mn<<2];
    int qry(int x,int s,int t,int p)
    {
        if(mx[p]<=x)return x*(t-s+1);
        if(s==t)return a[p];
        if(mx[lc]<=x)return x*(mid-s+1)+qry(x,mid+1,t,rc);
        return a[p]-a[lc]+qry(x,s,mid,lc);
    }
    void pup(int s,int t,int p)
    {
        a[p]=a[lc]+qry(mx[lc],mid+1,t,rc);
        mx[p]=max(mx[lc],mx[rc]);
    }
    void build(int s,int t,int p)
    {
        if(s==t)return void(a[p]=mx[p]=lst[s]);
        build(s,mid,lc);
        build(mid+1,t,rc);
        pup(s,t,p);
    }
    void upd(int x,int c,int s,int t,int p)
    {
        if(s==t)return void(mx[p]=a[p]=c);
        if(x<=mid)upd(x,c,s,mid,lc);
        else upd(x,c,mid+1,t,rc);
        pup(s,t,p);
    }
    pii qry(int l,int r,int s,int t,int p,int x)
    {
        if(l<=s && t<=r)return make_pair(qry(x,s,t,p),max(mx[p],x));
        pii res=make_pair(0,-0x3f3f3f3f);
        if(l<=mid)res=qry(l,r,s,mid,lc,x);
        if(r>mid)
        {
            if(res.second==-0x3f3f3f3f)res=qry(l,r,mid+1,t,rc,x);
            else
            {
                pii tmp=qry(l,r,mid+1,t,rc,max(x,res.second));
                res.first+=tmp.first;
                res.second=max(res.second,tmp.second);
            }
        }
        return res;
    }
}seg;
set<int> s[mn];
// 我们直接用 set 来更新 lst。注意到最多更新三次,所以这个复杂度是对的。
void upd(int x,int c)
{
    set<int>::iterator it=s[a[x]].erase(s[a[x]].lower_bound(x));
    if(it!=s[a[x]].end())
    {
        int t=*it;
        seg.upd(t,*--it,0,n-1,1);
    }
    s[a[x]=c].insert(x);
    it=++s[a[x]].lower_bound(x);
    if(it!=s[a[x]].end())
    {
        int t=*it;
        seg.upd(t,*--it,0,n-1,1);
    }
    else --it;
    int t=*it;
    seg.upd(t,*--it,0,n-1,1);
}
signed main()
{
    scanf("%lld %lld",&n,&q);
    for(int i=0;i<n;i++)s[i].insert(-1);
    for(int i=0;i<n;i++){
        scanf("%lld",&a[i]);
        lst[i]=*--s[a[i]].end();
        s[a[i]].insert(i);
    }
    seg.build(0,n-1,1);
    int op,x,y;
    for(int i=1;i<=q;i++)
    {
        scanf("%lld %lld %lld",&op,&x,&y);
        if(op)printf("%lld\n",(y+x-1)*(y-x)/2-seg.qry(x,y-1,0,n-1,1,x-1).first);
        else upd(x,y);
    }
}

第一代图灵机

首先我们发现,这题和上一题好像啊。那我们继续记 \(pre_i\) 表示与第 \(i\) 个位置颜色相同的上一个下标。

由于 \(a\) 非负,我们很容易发现最优解总可以用一个极大可行子区间来表示。

我们容易发现以 \(i\) 为右端点的极大可行子区间左端点即为 \(\max_{j=1}^ipre_j+1\)。所以点 \(i\) 的贡献即为 \(sum_i-sum_{\max_{j=1}^ipre_j}\)

所以我们要拿线段树维护这个东西。和上一题几乎一样吧。但是要注意由于我们存储的答案是取 max 得到的,我们无法在发现 mx[lc]>x 后像第一题 return a[p]-a[lc]+qry(x,s,mid,lc); 一样求答案。解决方案也很简单,再开一个数组即可。

int qry(int x,int s,int t,int p)
{
    if(mx[p]<=x)return sum[t]-sum[x];
    if(s==t)return a[p];
    if(mx[lc]<=x)return max(qry(x,s,mid,lc),qry(x,mid+1,t,rc));
    return max(qry(x,s,mid,lc),ta[p]);
}
void pup(int s,int t,int p)
{
    mx[p]=max(mx[lc],mx[rc]);
    ta[p]=qry(mx[lc],mid+1,t,rc);
    a[p]=max(a[lc],ta[p]);
}
完整代码
#include<cstdio>
#include<algorithm>
#include<set>
#include<utility>
#define int long long
#define lc (p<<1)
#define rc (p<<1|1)
#define mid ((s+t)>>1)
using namespace std;
typedef pair<int,int> pii;
const int mn=2e5+5;
int n,m,q;
int a[mn],c[mn];
int sum[mn];
int lst[mn];
set<int> s[mn];
struct SEG
{
    struct sta
    {
        int a,mx,ta;
    };
    int a[mn<<2],mx[mn<<2],ta[mn<<2];
    int qry(int x,int s,int t,int p)
    {
        if(mx[p]<=x)return sum[t]-sum[x];
        if(s==t)return a[p];
        if(mx[lc]<=x)return max(qry(x,s,mid,lc),qry(x,mid+1,t,rc));
        return max(qry(x,s,mid,lc),ta[p]);
    }
    void pup(int s,int t,int p)
    {
        mx[p]=max(mx[lc],mx[rc]);
        ta[p]=qry(mx[lc],mid+1,t,rc);
        a[p]=max(a[lc],ta[p]);
    }
    void build(int s,int t,int p)
    {
        if(s==t)
        {
            a[p]=sum[s]-sum[lst[s]];
            mx[p]=lst[s];
            return;
        }
        build(s,mid,lc);
        build(mid+1,t,rc);
        pup(s,t,p);
    }
    void upd(int x,int c,int s,int t,int p)
    {
        if(s==t)
        {
            a[p]=sum[s]-sum[c];
            mx[p]=c;
            return;
        }
        if(x<=mid)upd(x,c,s,mid,lc);
        else upd(x,c,mid+1,t,rc);
        pup(s,t,p);
    }
    pii qry(int l,int r,int s,int t,int p,int x)
    {
        if(l<=s && t<=r)return make_pair(qry(x,s,t,p),mx[p]);
        pii res=make_pair(0,-0x3f3f3f3f);
        if(l<=mid)res=qry(l,r,s,mid,lc,x);
        if(r>mid)
        {
            if(res.second==-0x3f3f3f3f)res=qry(l,r,mid+1,t,rc,x);
            else
            {
                pii tmp=qry(l,r,mid+1,t,rc,max(x,res.second));
                res.first=max(res.first,tmp.first);
                res.second=max(res.second,tmp.second);
            }
        }
        return res;
    }
}seg;
void upd(int x,int y)
{
    set<int>::iterator it=s[c[x]].erase(s[c[x]].lower_bound(x));
    if(it!=s[c[x]].end())
    {
        int t=*it;
        seg.upd(t,*--it,1,n,1);
    }
    s[c[x]=y].insert(x);
    it=++s[c[x]].lower_bound(x);
    if(it!=s[c[x]].end())
    {
        int t=*it;
        seg.upd(t,*--it,1,n,1);
    }
    else --it;
    int t=*it;
    seg.upd(t,*--it,1,n,1);
}
signed main()
{
    // freopen("norepeat.in","r",stdin);
    // freopen("norepeat.out","w",stdout);
    scanf("%lld %lld %lld",&n,&m,&q);
    for(int i=1;i<=n;i++){
        s[i].insert(0);
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&c[i]);
        lst[i]=*--s[c[i]].end();
        s[c[i]].insert(i);
    }
    seg.build(1,n,1);
    int op,x,y;
    for(int i=1;i<=q;i++)
    {
        scanf("%lld %lld %lld",&op,&x,&y);
        if(op==1)printf("%lld\n",seg.qry(x,y,1,n,1,x-1).first);
        else upd(x,y);
    }
}
posted @ 2025-08-07 22:39  ikusiad  阅读(2)  评论(0)    收藏  举报