维护逆(顺)序对(组)问题
与顺序相关的 \(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;
}