CF1602E Optimal Insertion
Statement
给定序列 \(a_n,b_m\) ,可以将 \(b\) 以任意顺序插入到 \(a\) 的任意位置里面,求插入后最少有多少的逆序对。
( \(a\) 不可交换顺序,逆序对算上了 \(a\) 本来的逆序对)
Solution
我们容易想到一个贪心思路:对 \(b\) 排序后,依次插入。
所谓的依次插入是指,假设 \(b_i\) 插入到了 \(p\) ,那么 \(b_{i+1}\) 插入到区间 \([p,n]\) 中最优位置
所谓的最优位置是指,\(b_i\) 插入后产生的逆序对数最少的位置
考虑证明这样的正确性,首先考虑为什么 \(b\) 应该是单调的

也就是证明红色点方式比黑色点方式更优
知道 \(b\) 本身产生了一个逆序对,考虑 \(a\) 中多少数会与之形成逆序对
- 对于区间 \([0,i)\cup (j,n]\) ,两种方式数量一样
- 对于区间 \((i,j)\)
- 值域 \([0,b_j)\cup(b_i,V]\) ,两种方式数量一样
- 值域 \((b_j,b_i)\), 显然黑色更优
再考虑是否存在一种情况,使得通过让 \(b_i\) 取得一个不那么优秀的位置,而让 \(b_{i+1}\) 取到一个最优位置会比我们的方式更优
设 \(p_i\) 表示 \(b_i\) 全局最优位置,假设 \(b_1,b_2\) 满足上述假设,那么一定有 \(p_2<p_1\)
因为如果 \(p_1<p_2\) ,那么两个数显然都可以放在自己的全局最优位置

即是证明 \(B\) 优于 \(A\) 。发现不是很好证,考虑从根源入手,真的存在 \(p_2<p_1\) 的情况吗?
我们记 \(c[i][j]\) 表示把 \(b_i\) 放到 \(p_j\) 对于区间 \([p2,p1]\) 新产生的逆序对个数
我们可以排除掉 \([1,p_2)\) 和 \((p_1,n]\) 这部分区间内 \(a\) 对逆序对的干扰,反正 \(b_1,b_2\) 怎么排那部分贡献都一样(这里仍然假设 \(p_2<p_1\))
由 \(p_1,p_2\) 的定义,我们知道 \(c[1][1]<c[1][2]\) ,\(c[2][2]<c[2][1]\)
考虑一个数插入到一个数列的个数是其左边比它大的数和其右边比它小的数,令比它小的数为 \(0\) ,比它大的数为 \(1\) ,既是左边 \(1\) 的个数加上右边 \(0\) 的个数
那么 \(c[1][1]\geq c[2][1]\),\(c[2][2]\geq c[2][1]\) 已经和上面发生矛盾了。
我们已经证明了贪心思路的正确性,现在的问题变成了怎么快速地求得 \(i=1...m\) 的 \(p_i\)
设 \(f_i\) 表示把 \(b_x\) 插入在 \(i\) 处产生的新逆序对数,假设我们得到了 \(b_x\) 意义下的 \(f\) ,考虑 \(b_{x+1}\)
对于每个\(j\) 满足 \(b_x\leq a_j\leq b_{x+1}\) ,让 \(f_{0\dots j-1}\) 全部加一,区间 \(f_{j+1\dots n}\) 全部减一即可
\(\to\) 解释:延续上面对 \(0,1\) 的定义 。\(b\) 变大,\(a_j\) 对于 \(b_x\) 为 \(1\),对于 \(b_{x+1}\) 为 \(0\) 。所以当 \(b_{x+1}\) 在 \([0,j-1]\) 的时候,产生的逆序对比 \(b_x\) 在 \([0,j-1]\) 的时候多了一个,另外一边就相对地少了一个。
那么 \(p_i\) 就是 \(f\) 的最小值了,同时也可以求出放 \(b_x\) 后新增逆序对数量。区间增加,区间查 \(min\) ,上线段树。
顺序遍历 \(a\) ,每一个 \(a_i\) 最多参与一次区间加减,总的还是 \(n\log n\)
至此,我们 \(n\log n\) 求出了把 \(b\) 加入 \(a\) 新增逆序对数的最小值,加上本来就有的逆序对数(树状数组 \(n\log n\) 可求)即可。
\(O(n\log n)\)
Code
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
const int N = 1e6+6;
int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
return s*w;
}
struct node{
int val,id;
node operator+(const node& rhs)const{
if(val<rhs.val)return *this;
if(val>rhs.val)return rhs;
return (node){val,min(id,rhs.id)};
}
};
struct Tree{
node mn; int tg;
}t[N<<2];
struct BIT{
int c[N];
int clear(){memset(c,0,sizeof(c));}
int lowbit(int x){return -x&x;}
void add(int x,int v){for(;x<N;x+=lowbit(x))c[x]+=v;}
int ask(int x){int r=0;for(;x;x-=lowbit(x))r+=c[x];return r;}
}bit;
int a[N],p[N],b[N];
int T,n,m;
long long ans;
bool cmp(int x,int y){return a[x]<a[y];}
void pushup(int rt){t[rt].mn=t[ls].mn+t[rs].mn;}
void pushdown(int rt){
if(!t[rt].tg)return ;
t[ls].mn.val+=t[rt].tg,t[ls].tg+=t[rt].tg;
t[rs].mn.val+=t[rt].tg,t[rs].tg+=t[rt].tg;
t[rt].tg=0;
}
void build(int l,int r,int rt){
t[rt].tg=0; int mid=(l+r)>>1;
if(l==r)return t[rt].mn={l,l},void();
build(l,mid,ls),build(mid+1,r,rs);
pushup(rt);
}
void change(int l,int r,int rt,int L,int R,int v){
if(L<=l&&r<=R)return t[rt].mn.val+=v,t[rt].tg+=v,void();
int mid=(l+r)>>1; pushdown(rt);
if(L<=mid)change(l,mid,ls,L,R,v);
if(mid<R)change(mid+1,r,rs,L,R,v);
pushup(rt);
}
node query(int l,int r,int rt,int L,int R){
if(R<l||r<L)return {(int)1e9,0};
if(L<=l&&r<=R)return t[rt].mn;
int mid=(l+r)>>1; pushdown(rt);
return query(l,mid,ls,L,R)+query(mid+1,r,rs,L,R);
}
signed main(){
T=read();
while(T--){
n=read(),m=read(),ans=0;
for(int i=1;i<=n;++i)a[i]=read(),p[i]=i;
for(int i=1;i<=m;++i)b[i]=read();
sort(p+1,p+1+n,cmp),sort(b+1,b+1+m),build(0,n,1);
int ptr1=1,ptr2=1,lim=0;
for(int i=1;i<=m;++i){
while(ptr1<=n&&a[p[ptr1]]<b[i])
change(0,n,1,0,p[ptr1]-1,1),ptr1++;
while(ptr2<=n&&a[p[ptr2]]<=b[i])
change(0,n,1,p[ptr2],n,-1),ptr2++;
node res=query(0,n,1,lim,n);
ans+=res.val,lim=res.id;
}
for(int i=1;i<=n;++i)p[i]=a[i];
sort(p+1,p+1+n);
for(int i=1;i<=n;++i)
a[i]=lower_bound(p+1,p+1+n,a[i])-p;
for(int i=1;i<=n;++i)
ans+=bit.ask(n)-bit.ask(a[i]),
bit.add(a[i],1);
for(int i=1;i<=n;++i)bit.add(a[i],-1);
printf("%lld\n",ans);
}
return 0;
}
这道题我们证明了一个重要结论:对于两个数 x,y ,它们插入某一个序列的最优位置单调
(最优位置:这里是指新增逆序对更少的位置)