P1966 [NOIP2013 提高组] 火柴排队

Label

一个经典贪心问题(排序不等式)与一个经典逆序对问题(乱序序列有序的最小相邻位交换次数)的组合

Description

给定两个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n \leq10^5) n(1n105)的数列 a , b ( 0 ≤ a i , b i ≤ 2 31 a,b(0\leq a_i,b_i\leq 2^{31} a,b(0ai,bi231且同一列火柴的高度互不相同 ) ) ),现规定可对 a , b a,b a,b执行任意此相邻位元素交换数值的操作,求最小的操作次数,使得 ∑ i = 1 n ( a i − b i ) 2 \sum_{i=1}^{n}(a_i-b_i)^2 i=1n(aibi)2最小化。答案对 1 0 8 − 3 10^8-3 1083取模。

Solution

考虑如何最小化 ∑ i = 1 n ( a i − b i ) 2 \sum_{i=1}^{n}(a_i-b_i)^2 i=1n(aibi)2

由于
∑ i = 1 n ( a i − b i ) 2 = ∑ i = 1 n ( a i 2 + b i 2 − 2 a i b i ) \sum_{i=1}^n(a_i-b_i)^2=\sum_{i=1}^n(a_i^2+b_i^2-2a_ib_i) i=1n(aibi)2=i=1n(ai2+bi22aibi)

∑ i = 1 n ( a i 2 + b i 2 ) \sum_{i=1}^n(a_i^2+b_i^2) i=1n(ai2+bi2)不受 a 、 b a、b ab元素排列顺序影响,故本题等价于令 ∑ i = 1 n a i b i \sum_{i=1}^{n}a_ib_i i=1naibi最大化。这个问题其实是就是排序不等式解决的问题,有如下结论:顺序之乘之和 ≥ \geq 乱序乘法之和 ≥ \geq 反序乘法之和。

证明此处排序不等式的证明方法为典型的微扰:

现有四个正数 a 1 , a 2 , b 1 , b 2 , a 1 < a 2 , b 1 < b 2 a_1,a_2,b_1,b_2,a_1<a_2,b_1<b_2 a1,a2,b1,b2,a1<a2,b1<b2,则必有 a 1 b 1 + a 2 b 2 > a 1 b 2 + a 2 b 1 a_1b_1+a_2b_2>a_1b_2+a_2b_1 a1b1+a2b2>a1b2+a2b1

证明:对上述不等式移项,发现其等价于 a 2 ( b 2 − b 1 ) > a 1 ( b 2 − b 1 ) a_2(b_2-b_1)>a_1(b_2-b_1) a2(b2b1)>a1(b2b1),由于 b 2 − b 1 > 0 且 a 2 > a 1 b_2-b_1>0且a_2>a_1 b2b1>0a2>a1,故该不等式显然成立,命题得证。

由于任意乱序数列可经多次顺序交换变为顺序数列,故此处微扰可推广到一般情况,成立。

对于此题,满足条件的 a a a b b b一定满足的条件是: ∀ i ∈ [ 1 , n ] , r a n k a i = r a n k b i \forall i\in[1,n],rank_{a_i}=rank_{b_i} i[1,n],rankai=rankbi,其中 r a n k rank rank表示 a 、 b a、b ab任意元素在各自序列内的值排名(默认最小数为1)。由于某个两列同时移动火柴的方案等价于只移动一列内火柴的方案,故我们不妨考虑如何移动 a a a内元素使其满足 r a n k a i = r a n k b i rank_{a_i}=rank_{b_i} rankai=rankbi。我们先对两个数列进行离散化,即令 a i = r a n k a i , b i = r a n k b i a_i=rank_{a_i},b_i=rank_{b_i} ai=rankai,bi=rankbi,之后,我们当前的目标是令 a a a序列与 b b b相等,故构造一个映射 a [ p l a a [ b [ i ] ] ] = i a[plaa[b[i]]]=i a[plaa[b[i]]]=i(值排名为i的数在对应序列的位置为 p l a a [ i ] / p l a b [ i ] plaa[i]/plab[i] plaa[i]/plab[i] ),我们令 a a a b b b相等便等价于使 a [ i ] = i a[i]=i a[i]=i恒成立,即将乱序的 a a a通过邻位交换变为升序数列。问题就变为,将原本乱的 a a a序列升序排列的最少交换次数。

这是一个经典问题,答案是 a a a内逆序对的个数。

证明:

1、存在一个方案使得交换次数等于 a a a内逆序对的个数:

交换方法:等价于冒泡排序—先将序列内最大的数交换到末位,然后将序列内次大的数交换到倒数第二位,依此类推。显然,每一个数(设其值为 k k k)被交换的次数等于从其在原数列位置往后小于它的元素的个数,即所有逆序对 ( i , j ) ( i > j ) (i,j)(i>j) (ij)(i>j)中, i = k i=k i=k的逆序对的个数,那么整个方案的交换次数便是整个序列内的逆序对个数了。

2、任意交换方案次数一定大于等于 a a a内逆序对的个数:此处考虑每一个数的位置交换即可。

综上,将原本乱的 a a a序列升序排列的最少交换次数是 a a a内逆序对的个数。命题得证

最后,需要注意的是:此题规定每列内火柴高度互不相同,这样一来省去了一些关于离散化与求逆序对个数时细节处理上的麻烦。

Code

#include<cstdio>
#include<iostream>
#include<algorithm>
#define ri register int
using namespace std;

const int MAXN=1e5+20,MOD=1e8-3;
int n,a[MAXN],plaa[MAXN],b[MAXN],plab[MAXN],tmp[MAXN],ans;
struct node{
	int val,num;
}s[MAXN];
bool cmp(const node &x,const node &y){
	return x.val < y.val;
}

void mergesort(int l,int r)//归并排序 
{
	if(l==r)	return;
	ri mid=(l+r)>>1;
	mergesort(l,mid); mergesort(mid+1,r);
	for(ri i=l;i<=r;++i)	tmp[i]=a[i];
	ri head=l,tail=mid+1,cnt=l;
	while(head<=mid&&tail<=r)
	{
		if(tmp[head]>tmp[tail])
			a[cnt++]=tmp[tail++];
		if(tail>r)	break;
		if(tmp[head]<=tmp[tail]) 
		{
			ans=(ans+tail-mid-1)%MOD;
			a[cnt++]=tmp[head++];
		}
	}
	if(head<=mid)
		for(ri i=head;i<=mid;++i)
		{
			a[cnt++]=tmp[i];
			ans=(ans+r-mid)%MOD;
		}
	if(tail<=r)
		for(ri i=tail;i<=r;++i)	a[cnt++]=tmp[i];
}

void init(int x[],int pla[])
{
	for(ri i=1;i<=n;++i)
	{
		scanf("%d",&s[i].val);
		s[i].num=i;
	}
	sort(s+1,s+n+1,cmp);
	for(ri i=1;i<=n;++i)
	{
		pla[i]=s[i].num;
		x[pla[i]]=i; 
	}
}

int main()
{
	scanf("%d",&n);
	init(a,plaa); init(b,plab);
	for(ri i=1;i<=n;++i)
		a[plaa[b[i]]]=i;
	mergesort(1,n);
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值