【逆序对相关/数学】【P1966】【NOIP2013D1T2】 火柴排队

传送门

Description

涵涵有两盒火柴,每盒装有 $n$ 根火柴,每根火柴都有一个高度。 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为:$ sum (a_i-b_i)^2$

其中$ a_i$ 表示第一列火柴中第$ i $个火柴的高度,$b_i$表示第二列火柴中第 $i$ 个火柴的高度。

每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 $99,999,997 $取模的结果。

## Input

共三行,第一行包含一个整数$ n$,表示每盒中火柴的数目。

第二行有$ n $个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。

第三行有 $n$ 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。

## Output

一个整数,表示最少交换次数对 $99,999,997$ 取模的结果。

## Sample Input ``` 4 2 3 1 4 3 2 1 4 ``` ## Sample Output ``` 1 ``` ## Hint 对于 $100\%$的数据,$1 ≤ n ≤ 100,000,0 ≤$火柴高度$≤ maxlongint$ ## Solution 和[NOIP2011D2T1聪明的质检员](https://www.cnblogs.com/yifusuyi/p/9531622.html)一样,既然题目给了一个表达式并且瞪眼看不出这个式子有啥可做的,那么就化简这个式子。

把式子平方展开,则$ sum (a_i-b_i)^2~=~sum~a_i^2~+~sum~b_i^2~+~2~ imes~sum~a_i*b_i$。

显然(sum~a_i^2~+~sum~b_i^2)已经固定了,所以我们要做的是最小化(sum~a_i*b_i)

根据排序不等式,上式最小当且仅当(forall i),a,b第(i)大的数在同一位置。

那么我们按照(a_i)的顺序给(b_i)排序。即新建一个序列(C),将a,b离散化后使得(C)满足(C_{a_i}=b_i)。它的数学意义是(a)中第i大的数对应位置上(b)的大小。

显然,达到合法的情况应该满足(forall i,C_i=i)

显然一起移动A,B和只移动B是等价的,那么本题就变为交换(C)中相邻元素用最少的步数满足(C)中元素按照升序排序。

咦?这不就是逆序对嘛?

其实我也是才知道这就是逆序对的意义

证明可以使用数学归纳法。
先将最大的元素移动到后面去。显然元素列他后面都是都是比他小的,所以把他移动到最后的步数就等于关于它的逆序对数。
他到最后去以后,下面的问题等价于将剩下n-1个数升序排列。
由于最大的数和后面每个数都进行了交换,所以除第n个数以外整个数列的相对的大小位置是不变的
这样就使用数学归纳法证明了上述问题的最小步数就是逆序对的个数。

Code

#include<cstdio>
#include<algorithm>
#define rg register
#define ci const int
#define cl const long long int

typedef long long int ll;

namespace IO {
	char buf[50];
}

template<typename T>
inline void qr(T &x) {
	char ch=getchar(),lst=' ';
	while(ch>'9'||ch<'0') lst=ch,ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	if (lst=='-') x=-x;
}

template<typename T>
inline void write(T x,const char aft,const bool pt) {
	if(x<0) {putchar('-');x=-x;}
	int top=0;
	do {
		IO::buf[++top]=x%10+'0';
		x/=10;
	} while(x);
	while(top) putchar(IO::buf[top--]);
	if(pt) putchar(aft);
}

template <typename T>
inline T mmax(const T a,const T b) {if(a>b) return a;return b;}
template <typename T>
inline T mmin(const T a,const T b) {if(a<b) return a;return b;}
template <typename T>
inline T mabs(const T a) {if(a<0) return -a;return a;}

template <typename T>
inline void mswap(T &a,T &b) {T temp=a;a=b;b=temp;}

const int maxn = 100010;
const int MOD = 99999997;

int n;
int a[maxn],b[maxn],c[maxn],d[maxn],frog[maxn],temp[maxn];
ll ans;

inline bool cmp1(const int &_a,const int &_b) {
	return a[_a]<a[_b];
}

inline bool cmp2(const int &_a,const int &_b) {
	return b[_a]<b[_b];
}

void ms(ci,ci);

int main() {
	qr(n);
	for(rg int i=1;i<=n;++i) qr(a[i]);
	for(rg int i=1;i<=n;++i) qr(b[i]);
	for(rg int i=1;i<=n;++i) c[i]=d[i]=i;
	std::sort(c+1,c+1+n,cmp1);std::sort(d+1,d+1+n,cmp2);
	for(rg int i=1;i<=n;++i) frog[c[i]]=d[i];
	ms(1,n);
	write(ans,'
',true);
	return 0;
}

void ms(ci l,ci r) {
	if(l>=r) return;
	int mid=(l+r)>>1;
	ms(l,mid);ms(mid+1,r);
	int i=l,j=mid+1,p=l;
	while((i<=mid) && (j<=r)) {
		if(frog[i]<frog[j]) temp[p++]=frog[i++];
		else temp[p++]=frog[j++],ans=(ans+mid-i+1)%MOD;
	}
	while(i<=mid) temp[p++]=frog[i++];
	while(j<=r) temp[p++]=frog[j++];
	for(i=l;i<=r;++i) frog[i]=temp[i];
}

Summary

1、遇见棘手的表达式,先化简再说
2、将一个序列交换相邻元素用最小步数使得序列按照升序排列的答案就是序列中的逆序对个数
3、代码中给出了离散化的方法以及归并排序求逆序对的方法。

原文地址:https://www.cnblogs.com/yifusuyi/p/9545700.html