Codeforces_1601C Optimal Insertion题解

Codeforces_1601C Optimal Insertion题解

题目传送门


经典分治题(几乎无坑)


题意:

给定数组a和数组b,保证a原序下向其中插入b(b顺序任意,即可乱序),问最终逆序对最少的对数


分析:

  1. 前提
    注意到数组b的插入一定是按照其大小顺序的。比如对于bi<bj且在插入后的位置也满足pi<pj,若交换其位置,则对于位置p<pi和p>pj(即在原来i,j位置两边的位置)上的元素,其形成的逆序对数量不变。同时,交换因为b本身的大小比较会产生一个逆序对,而在pi与pj间的元素原本与bi形成逆序对的仍然可以与bj形成逆序对(因为设该元素为t,则有t<bi<bj)。同理,原来与bj形成逆序对的元素仍可与bi形成逆序对。故中间逆序对数量只增不减。综上,交换位置一定会引起逆序对数量的增多,所以b要依大小顺序插入。
  2. 分治
    每次操作一个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的最大值即可。
  1. 统计结果
    这部分可以直接根据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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hiroxzwang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值