维护逆(顺)序对(组)问题

与顺序相关的 \(2\) 元组求值问题,往往利用线段树进行维护,算一种经典的操作。


我们不妨先从一道简单的问题入手:求数列逆序对数。

树状数组是一个不错的选择,利用其前缀和性质维护桶,倒着扫,边扫边加数,同时查询,非排列再离散化一下,问题在 \(\Theta(n\log n)\) 复杂度内得解。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],ans(0);
int t[N];
namespace BIT{
    int lowbit(int x){return x&(-x);}
    void add(int x){for(;x<=n;x+=lowbit(x)) t[x]++;}
    int Qry(int x){
        int res(0);
        for(;x>0;x-=lowbit(x)) res+=t[x];
        return res;
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    for(int i=n;i;i--){
        ans+=BIT::Qry(a[i]);
        BIT::add(a[i]);
    }
    printf("%d\n",ans);
    return 0;
}

现在我们将问题扩展一下:对于数列中每个二元组 \((i,j)\),满足 \(i<j\),现在我们最大化 \(a_i-a_j\),一样的思路,找逆序对以最大化价值,但 \(\Theta(n^2)\) 维护最大逆序对不够优秀,考虑线段树 \(\Theta(n\log n)\) 维护这个最大答案:

比较容易:我们维护区间最大 \(maxn\),最小 \(minn\),以及最大答案 \(ans\),那么父节点的答案为左右区间答案取 \(max\),再与左区间 \(maxn\) 减右区间 \(minn\) 比较,即 \(ans_{p}=max(ans_{ls},ans_{rs},maxn_{ls}-minn_{rs})\)

但仅仅维护最大答案的值往往是不够的,我们考虑如何维护构成最优答案的某一个解。显然解的位置与最值位置有关,那我们在原来的基础上再维护最值位置 \(mxid\)\(mnid\),以及最大答案位置 \(ansl\)\(ansr\)push_up 就比较平凡了,就是在前面的基础上将位置信息同时维护即可。

静态维护还是不够,那就带修,怎么办呢?加个 \(tag\) 不就行了?

于是,我们得到了这类问题的一般解法,下面就是一个带区间加的代码示例:

struct Node{
    #define lson pos<<1
    #define rson pos<<1|1
    int maxn,minn,ans,tag,mxid,mnid;
    pair<int,int> ansid;
}t[N<<2];
struct TREE{
    Node merge(Node A,Node B){
        Node res;
        res.maxn=max(A.maxn,B.maxn);
        res.minn=min(A.minn,B.minn);
        if(A.maxn>=B.maxn) res.mxid=A.mxid;
        else res.mxid=B.mxid;
        if(A.minn<=B.minn) res.mnid=A.mnid;
        else res.mnid=B.mnid;
        res.ans=max({A.ans,B.ans,A.maxn-B.minn});
        if(res.ans==A.ans) res.ansid=A.ansid;
        else if(res.ans==B.ans) res.ansid=B.ansid;
        else res.ansid={A.mxid,B.mnid};
        return res;
    }
    void push_up(int pos){
        t[pos]=merge(t[lson],t[rson]);
    }
    void push_down(int pos){
        if(!t[pos].tag) return;
        t[lson].minn+=t[pos].tag;
        t[rson].minn+=t[pos].tag;
        t[lson].maxn+=t[pos].tag;
        t[rson].maxn+=t[pos].tag;
        t[lson].tag+=t[pos].tag;
        t[rson].tag+=t[pos].tag;
        t[pos].tag=0;
    }
    void build(int pos,int l,int r){
        t[pos].tag=0;
        if(l==r) return t[pos]={a[l],a[l],0,0,l,l,{l,l}},void();
        int mid((l+r)>>1);
        build(lson,l,mid);
        build(rson,mid+1,r);
        push_up(pos);
    }
    void modify(int pos,int l,int r,int ql,int qr,int k){
        if(ql<=l&&r<=qr){
            t[pos].minn+=k;
            t[pos].maxn+=k;
            t[pos].tag+=k;
            return;
        }
        push_down(pos);
        int mid((l+r)>>1);
        if(ql<=mid) modify(lson,l,mid,ql,qr,k);
        if(mid<qr) modify(rson,mid+1,r,ql,qr,k);
        push_up(pos);
    }
    Node query(int pos,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr) return t[pos];
        push_down(pos);
        int mid((l+r)>>1);
        if(qr<=mid) return query(lson,l,mid,ql,qr);
        else if(ql>mid) return query(rson,mid+1,r,ql,qr);
        else return merge(query(lson,l,mid,ql,qr),query(rson,mid+1,r,ql,qr));
    }
}S;

例题:


C. 最小生成树(mst / 1s / 512 MiB)

在图论中,无向图 \(G\) 的生成树,是具有 \(G\) 的全部顶点,但边数最少连通子图

\(G\) 的最小生成树,即为 \(G\) 中所有的生成树中,所有边的边权和最小的一棵生成树。

给定一个数组 \(x_1,x_2,\cdots, x_n\)。构造一张 \(n\) 个点的完全图。对于任意 \(1 \le i < j \le n\),图中有一条边权为 \(x_j - x_i\) 的无向边。

你想要求出这张无向图的最小生成树的边权之和。


题解:显然最小生成树是由一系列逆序对构成的,但是这个联通关系该怎么办呢?我们从左向右加点,每加一次,下一次就将但前加入的点作为新的起点,这样就维护了连通性。那么,当答案产生时,一定会·有如下性质:在右区间时比当前点更小,左区间时比当前点更大,这样分别建立两颗线段树维护左右不同区间的逆序关系,贪心即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define inf 1e13
using namespace std;
const int N=3e5+10;
int n,ans(0);
int a[N];
struct TREE{
    struct Node{
        #define lson pos<<1
        #define rson pos<<1|1
        int maxn,minn,ans,mxid,mnid,ansl,ansr;
        Node(int maxn_=0,int minn_=0,int ans_=-inf,int mxid_=0,int mnid_=0,int ansl_=0,int ansr_=0){
            maxn=maxn_,minn=minn_,ans=ans_,mxid=mxid_,mnid=mnid_,ansl=ansl_,ansr=ansr_;
        }
    }t[N<<2];
    Node push_up(Node A,Node B){
        Node Now;
        Now.maxn=max(A.maxn,B.maxn);
        Now.minn=min(A.minn,B.minn);
        Now.mxid=A.maxn>B.maxn?A.mxid:B.mxid;
        Now.mnid=A.minn<B.minn?A.mnid:B.mnid;
        Now.ans=max({A.ans,B.ans,A.maxn-B.minn});
        if(Now.ans==A.ans){
            Now.ansl=A.ansl;
            Now.ansr=A.ansr;
        }
        else if(Now.ans==B.ans){
            Now.ansl=B.ansl;
            Now.ansr=B.ansr;
        }
        else if(Now.ans==A.maxn-B.minn){
            Now.ansl=A.mxid;
            Now.ansr=B.mnid;
        }
        return Now;
    }
    void build(int pos,int l,int r,int op){
        if(l==r){
            if(!op) return t[pos]=Node(-inf,a[l],t[pos].ans,0,l,t[pos].ansl,t[pos].ansr),void();
            else return t[pos]=Node(a[l],inf,t[pos].ans,l,0,t[pos].ansl,t[pos].ansr),void();
        }
        int mid((l+r)>>1);
        build(lson,l,mid,op);
        build(rson,mid+1,r,op);
        t[pos]=push_up(t[lson],t[rson]);
    }
    void modify(int pos,int l,int r,int x,int op){
        if(l==r){
            if(!op) return t[pos]=Node(-inf,a[x],t[pos].ans,0,x,t[pos].ansl,t[pos].ansr),void();
            else return t[pos]=Node(a[x],inf,t[pos].ans,x,0,t[pos].ansl,t[pos].ansr),void();
        }
        int mid((l+r)>>1);
        if(x<=mid) modify(lson,l,mid,x,op);
        else modify(rson,mid+1,r,x,op);
        t[pos]=push_up(t[lson],t[rson]);
    }
}MAX,MIN;
signed main(){
    freopen("mst.in","r",stdin);
	freopen("mst.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1;i<=n;++i) scanf("%lld",a+i);
    MAX.build(1,1,n,1);
    MIN.build(1,1,n,0);
    for(int i=1,now(1);i<n;++i){
        MAX.modify(1,1,n,now,0);
        MIN.modify(1,1,n,now,1);
        if(MAX.t[1].ans>MIN.t[1].ans) now=MAX.t[1].ansl,ans+=MAX.t[1].ans;
        else now=MIN.t[1].ansr,ans+=MIN.t[1].ans;
    }
    printf("%lld\n",-ans);
    return 0;
}

posted @ 2023-10-16 07:32  Melting_Pot  阅读(30)  评论(0编辑  收藏  举报