Codeforces_1601C Optimal Insertion题解
经典分治题(几乎无坑)
题意:
给定数组a和数组b,保证a原序下向其中插入b(b顺序任意,即可乱序),问最终逆序对最少的对数
分析:
- 前提
注意到数组b的插入一定是按照其大小顺序的。比如对于bi<bj且在插入后的位置也满足pi<pj,若交换其位置,则对于位置p<pi和p>pj(即在原来i,j位置两边的位置)上的元素,其形成的逆序对数量不变。同时,交换因为b本身的大小比较会产生一个逆序对,而在pi与pj间的元素原本与bi形成逆序对的仍然可以与bj形成逆序对(因为设该元素为t,则有t<bi<bj)。同理,原来与bj形成逆序对的元素仍可与bi形成逆序对。故中间逆序对数量只增不减。综上,交换位置一定会引起逆序对数量的增多,所以b要依大小顺序插入。 - 分治
每次操作一个b的区间(u,v),a的区间(l,r),取mid=(u+v),在(l,r)中找到mid对应的最佳位置(也就是生成逆序对最少的位置)。然后再去查询子区间 b-(u,mid-1),a-(l,pmid)和b-(mid+1,v),a-(pmid,r)对应答案就好。
- 分治正确性
(当前最优pmid对后续分治依然最优的正确性)这里不妨设想一下pmid取当前值加1的情况,也就是最终bmid插入位置右移一下的结果。首先,右移可能造成bmid与apmid形成新的逆序对。其次对于i<mid,bi的插入只是多了一个pmid的选项。如果插入在了这里,对于原来的情况就是插入在了最右边pmid-1的位置再加上与apmid形成逆序对的影响。然而pmid原来是mid的最优解,即bi<=bmid<=apmid,则将i插入在这里导致的逆序对数量不会减少。其他情况同理,所以mid不放在pmid上导致最终逆序对数量只增不减。
(只凭借当前区间逆序对寻找pmid的正确性)在每一次找到mid对应的最佳位置后,进行左右的分治。而左右分治不必考虑当前区间以外的a或b造成的逆序对影响,因为b顺序一定不会有逆序对,而a形成的逆序对则因b顺序的固定和之前递归pmid的确定而确定。换句话说,在当前区间外的数值与当前将要插入的b所造成的逆序对数量是常数。所以只需要在当前区间寻找当前mid的最大值即可。
- 统计结果
这部分可以直接根据p暴力生成插入后的序列,再用线段树一类的统计一下逆序对即为答案。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define lson rec<<1,l,mid
#define rson rec<<1|1,mid+1,r
using namespace std;
const int mx=2000005;
int t,n,m,fx;
int a[mx],b[mx],p[mx],c[mx],ps[mx],sm[mx<<2];
struct node{
int id,w;
bool operator<(const node &pt)
const{
return w>pt.w;
}
}xu[mx];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
return x*f;
}
void work(int u,int v,int l,int r)
{
if(u>v) return;
int po=(u+v)>>1;
int mn,rp=l,cnt=0;
for(int i=l;i<r;++i) cnt+=(a[i]<b[po])?1:0;
mn=cnt;
for(int i=l;i<r;++i){
if(a[i]<b[po]) --cnt;
else if(a[i]>b[po]) ++cnt;
if(mn>cnt) mn=cnt,rp=i+1;
}
p[po]=rp;
work(u,po-1,l,rp);work(po+1,v,rp,r);
}
void clear(int rec,int l,int r)
{
sm[rec]=0;
if(l==r) return;
int mid=(l+r)>>1;
clear(lson);clear(rson);
}
void modify(int rec,int l,int r)
{
if(l==r){
++sm[rec];return;
}
int mid=(l+r)>>1;
if(fx<=mid) modify(lson);
else modify(rson);
sm[rec]=sm[rec<<1]+sm[rec<<1|1];
}
int query(int rec,int l,int r)
{
if(fx>=r) return sm[rec];
int mid=(l+r)>>1;
if(fx<=mid) return query(lson);
else return query(rson)+sm[rec<<1];
}
int main()
{
t=read();
for(int op=1;op<=t;++op){
n=read();m=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=m;++i) b[i]=read();
sort(b+1,b+1+m);
work(1,m,1,n+1);
int np=1,cnt=0;
for(int i=1;i<=n;++i){
while(np<=m&&p[np]<=i){
c[++cnt]=b[np];
xu[cnt].id=cnt;xu[cnt].w=b[np];++np;
}
c[++cnt]=a[i];
xu[cnt].id=cnt;xu[cnt].w=a[i];
}
while(np<=m){
c[++cnt]=b[np];
xu[cnt].id=cnt;xu[cnt].w=b[np];++np;
}
sort(xu+1,xu+1+cnt);clear(1,1,cnt);
for(int i=1;i<=cnt;++i){
ps[xu[i].id]=i;
if(xu[i].w==xu[i-1].w)
ps[xu[i].id]=ps[xu[i-1].id];
}
ll ans=0;
for(int i=1;i<=cnt;++i){
fx=ps[i]-1;
if(fx) ans+=query(1,1,cnt);
++fx;modify(1,1,cnt);
}
printf("%lld\n",ans);
}
return 0;
}