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 ,它們插入某一個序列的最優位置單調
(最優位置:這里是指新增逆序對更少的位置)