1. 程式人生 > 實用技巧 >火柴排隊「逆序對」

火柴排隊「逆序對」

火柴排隊「逆序對」

題目描述

涵涵有兩盒火柴,每盒裝有 \(n\) 根火柴,每根火柴都有一個高度。 現在將每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 兩列火柴之間的距離定義為:\(\sum (a_i-b_i)^2\)

其中 \(a_{i}\)​ 表示第一列火柴中第 \(i\)個火柴的高度,\(b_i\) 表示第二列火柴中第 \(i\) 個火柴的高度。

每列火柴中相鄰兩根火柴的位置都可以交換,請你通過交換使得兩列火柴之間的距離最小。請問得到這個最小的距離,最少需要交換多少次?如果這個數字太大,請輸出這個最小交換次數對 \(10^8-3\) 取模的結果。

輸入格式

共三行,第一行包含一個整數 \(n\)

,表示每盒中火柴的數目。

第二行有 \(n\) 個整數,每兩個整數之間用一個空格隔開,表示第一列火柴的高度。

第三行有 \(n\) 個整數,每兩個整數之間用一個空格隔開,表示第二列火柴的高度。

輸出格式

一個整數,表示最少交換次數對 \(10^8-3\) 取模的結果。

輸入輸出樣例

輸入 #1

4
2 3 1 4
3 2 1 4

輸出 #1

1

輸入 #2

4
1 3 4 2
1 7 2 4

輸出 #2

2

思路分析

  • 雖然題目要求的是讓我們求方案數,但是首先我們得知道什麼樣的情況下兩個火柴的排列順序是符合題目條件的。
  • 怎麼讓\(\sum(a_i-b_i)^2\)最小?我們試著將其展開,就成了\(\sum a_i^2\)
    \(+\)\(b_i^2\)\(-2a_ib_i\)\(\sum a_i^2\)\(+\)\(b_i^2\)是一個定值,所以我們要求\(\sum -2a_ib_i\)的最小值,即求\(\sum 2a_ib_i\)的最大值
  • 我們當然要優先考慮把a序列和b序列相對本序列的高度相同的放在一起,用小學數學反證法證明:若\(a<b\),\(c<d\) ,則\(ac+bd\)一定是最大的。如果不是,說明\(ad+bc\)更大,即\(ac+bd<ad+bc\)

\[則 ac-ad < bc -bd \]

\[即 a > b \]

  • 到這裡顯然就已經產生了衝突,所以要保持兩個序列排名相同的放在相同位置
  • 既然要維護相同位置,那麼以一個序列為關鍵字對另一個序列排序即可。已知兩個序列\(a,b\)我們新建一個序列\(c\),首先將\(a\) \(b\)根據高度排序,然後以原位置為關鍵字處理\(c\),即令\(c[a[i].id] = b[i].id\)?為什麼要這麼處理,因為我們講兩個序列排序好了以後,\(i\)位置相對高度是一樣的,\(id\)存的是在排序之前的位置,如果之前位置相同那麼就有\(c[i] = i\),而偏離的位置就成了一個逆序對,只需求出有多少個逆序對就可找出要交換多少次。
  • 眾所周之,逆序對常見的有兩種做法,一個歸併排序,一個樹狀陣列。雖然時間效率都是\(O(nlogn)\),但歸併排序演算法貌似更優,但這裡使用樹狀陣列。因為我是根據樹狀陣列標籤找到的這題來練樹狀陣列的
  • 不懂的戳這裡:樹狀陣列求逆序對

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 500010
using namespace std;
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<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int mod = 99999997;
int n,c[N],ans,tree[N];
struct node{
	int num,id;
}a[N],b[N];
bool cmp(node a,node b){
	return a.num < b.num;
}
int lowbit(int x){
	return x & (-x);
}
void update(int x,int val){
	while(x <= n){
		tree[x] += val;
		x += lowbit(x);
	}
}
int getsum(int x){
	int res = 0;
	while(x > 0){
		res += tree[x];
		x -= lowbit(x);
	}
	return res;
}

int main(){
	n = read();
	for(int i = 1;i <= n;i++){
		a[i].num = read();
		a[i].id = i;
	}
	for(int i = 1;i <= n;i++){
		b[i].num = read();
		b[i].id = i;
	}
	sort(a+1,a+1+n,cmp);
	sort(b+1,b+1+n,cmp);
	for(int i = 1;i <= n;i++){
		c[a[i].id] = b[i].id;
	}
	for(int i = 1;i <= n;i++){ //樹狀陣列求逆序對
		update(c[i],1);
		ans = (ans+i-getsum(c[i]))%mod;
	}
	printf("%d\n",ans);
	return 0;
}