[luogu p1966] 火柴排序
\(\mathtt{Link}\)
\(\mathtt{Summarization}\)
給定兩個長度都為 \(n\) 的序列,求交換兩個陣列中相鄰數,使得 \(\sum_{i=1}^n(a_i-b_i)^2\) 最小化的最小步數。
\(\mathtt{Solution}\)
這題分為兩個部分,
- 第一個部分是,使得 \(\sum_{i=1}^n(a_i-b_i)^2\) 最小化的順序是什麼?
- 第二個部分是,調整到這種順序最少需要多少步?
那咱們先解決第一個部分再來解決第二個部分:
\(\mathcal{Part 1}\)
看這個式子覺得毫無頭緒,不如咱們用完全平方差拆開一下這個式子:
顯然,無論怎麼調整 \(a\) 和 \(b\)
有一個定理,當 \(a\),\(b\) 中對於每一個位置 \(i\) 都有 \(a_i\) 是 \(a\) 中第 \(k\) 小的,而 \(b_i\) 也是 \(b\) 中第 \(k\) 小的時,\(\sum_{i=1}^na_ib_i\) 最大。(本題已經規定所有火柴高度都是正整數了)
簡證:(預設 \(a, b\)
所有數都是正整數,因為資料範圍已經體現)取 \(a_1 \le a_2, b_1 \le b_2\), 可得 \(a_2(b_2-b_1) \ge a_1(b_2-b_1)\)。把這個式子變一下形可以得到:\(a_1b_1+a_2b_2 \ge a_1b_2+a_2b_1\)。推廣這個結論到多個數便可以得到結論了。
好,現在我們已經知道最優的情況是怎麼排列了,讓同一個位置的兩個陣列中的數在原陣列中的大小地位相同即可。那麼調換的最小步數又該怎麼求呢?我們進入第二部分:
\(\mathcal{Part2}\)
我們可以考慮這樣一種做法,
- 記錄每個火柴(數字)的編號和地位。(編號指之前在這個序列的第幾號,地位指是這個序列的第幾小的)(此處可以用結構體實現,待會code實現就明白了)
- 定義一個新陣列 \(q\),規定下標為 A火柴中地位為 \(i\) 的編號 的值為B火柴中地位為 \(i\) 的編號。那麼 \(q\) 陣列中的逆序對數量就是答案。
估計你此刻一定有114514個問題要問,別急,蟹蟹舉個例子慢慢說。
就按照樣例1來吧:
2 3 1 4
3 2 1 4
那麼將 \(a, b\) 排序,此時位於第 \(i\) 個位置上的數就是地位為 \(i\) 的數了。
排序後進行處理:
2 3 1 4 -> 1 2 3 4 -> 原編號:3 1 2 4
3 2 1 4 -> 1 2 3 4 -> 原編號:3 2 1 4
牢記這個式子:q[a[i].num] = b[i].num;
可得
q[a[1].num] = b[1].num -> q[3] = 3
q[a[2].num] = b[2].num -> q[1] = 2
q[a[3].num] = b[3].num -> q[2] = 1
q[a[4].num] = b[4].num -> q[4] = 4
q陣列的值分別為:2 1 3 4
q陣列中共有1個逆序對(2, 1)
因此答案為 1
好,現在你應該理解這個方法是什麼意思了,接下來我們來看為什麼這樣做是對的。
首先,我們來看逆序對的定義。
\(i < j, a_i > a_j\)
轉化為此題中的 \(q\) 陣列,就可以說a[i].num > a[j].num, b[i].num < b[j].num。這說明了什麼?我們以a為基準調整b,那麼b[i]和b[j]的順序就是反的,需要別過來。
還有一個定理,如果要讓有 \(p\) 個逆序對的序列通過兩兩交換變成上升序列,那麼需要操作 \(p\) 次。(可以從必有逆序對緊挨定理得出,每次交換緊挨的逆序對即可)
那麼就可以得出答案就和 \(q\) 陣列逆序對數量相等了。
如何求逆序對數量?
https://www.luogu.com.cn/problem/P1908
\(\mathtt{Code}\)
/*
* @Author: crab-in-the-northeast
* @Date: 2020-11-01 17:04:02
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2020-11-01 17:04:02
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
const int maxn = 100005;
const int mod = (int)1e8 - 3;
struct match {
int height;
int num;
const bool operator < (match &b) {
return this -> height < b.height;
}
}a[maxn], b[maxn];
long long ans = 0;
int q[maxn], tmp[maxn];
void merge_sort(int l, int r) {
if (l == r)
return ;
int mid = l + r >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
int i = l, j = mid + 1, k = l;
while (i <= mid && j <= r) {
if (q[i] <= q[j])
tmp[k++] = q[i++];
else {
tmp[k++] = q[j++];
ans = (ans - i + mid + 1) % mod;
}
}
while (i <= mid)
tmp[k++] = q[i++];
while (j <= r)
tmp[k++] = q[j++];
for (int i = l; i <= r; ++i)
q[i] = tmp[i];
return ;
}
int main() {
int n;
std :: scanf("%d", &n);
for (int i = 1; i <= n; ++i)
std :: scanf("%d", &a[i].height);
for (int i = 1; i <= n; ++i)
std :: scanf("%d", &b[i].height);
for (int i = 1; i <= n; ++i)
a[i].num = b[i].num = i;
std :: sort(a + 1, a + 1 + n);
std :: sort(b + 1, b + 1 + n);
for (int i = 1; i <= n; ++i)
q[a[i].num] = b[i].num;
merge_sort(1, n);
std :: printf("%lld\n", ans);
return 0;
}
\(\mathtt{More}\)
這種問題一看就是要分Part討論的,要注意分Part討論的題一定不要合在一起討論。不然你會亂!!
然後看到相鄰交換使得序列有序之類的,就要從逆序對數量這個角度考慮了。(雖然這種情況有點特殊)