题意
给一个序列
a
i
a_i
ai。
要找到两种不同的整个序列的两两匹配,使得所有两两匹配的差的和最小,输出这个和。
思路
经过几次思考和画画发现,其实最佳的匹配策略只有把原来的序列升序排序后,分为长度为4的块和长度为6的块,每个块内部做两种不同的排列,才能使得总cost最小(正确性待证)
那么,对长度为4的块,两种匹配方法为 1-2 3-4,1-4,2-3;对长度为6的块,两种匹配的方法为1-2 3-4 5-6,1-3 2-5 4-6。(匹配方法或许不唯一,但懒得再举例。。
可以发现cost最小的匹配满足:总花费 = ( m a x ( a i ) − m i n ( a i ) ) ∗ 2 (max(ai) - min(a_i) ) * 2 (max(ai)−min(ai))∗2。
回到原来的序列,因为n为偶数,假设原来的整个序列为一起进行两两匹配,那它原始的花费在理想情况下应该也为 ( m a x ( a i ) − m i n ( a i ) ) ∗ 2 (max(ai) - min(a_i) ) * 2 (max(ai)−min(ai))∗2。
但是由于我们发现可以把原序列长度拆成 2 k = 4 a + 6 b 2k = 4a + 6b 2k=4a+6b,即a个长度为4的区间和b个长度为6的区间。而相比原始的cost,减少的就是每个区间裂开的点的值。那我们用dp数组状态转移一下: d p [ i ] = m a x ( d p [ i − 4 ] + 裂 开 [ i − 1 , i ] , d p [ i − 6 ] + 裂 开 [ i − 1 , i ] , d p [ i − 1 ] ) dp[i] = max(dp[i-4] + 裂开[i-1,i], dp[i-6] + 裂开[i-1,i], dp[i-1]) dp[i]=max(dp[i−4]+裂开[i−1,i],dp[i−6]+裂开[i−1,i],dp[i−1])。
最终答案就是 ( m a x ( a i ) − m i n ( a i ) ) ∗ 2 − d p [ n ] ∗ 2 (max(ai) - min(a_i) ) * 2 - dp[n]*2 (max(ai)−min(ai))∗2−dp[n]∗2。
O ( n l o g n ) O(nlogn) O(nlogn)排个序, O ( n ) O(n) O(n)转移下状态输出结果。
代码
/*
* @ author: dragon_bra
* @ email: tommy514@foxmail.com
* @ data: 2020-07-18 12:51
*/
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-5;
const int N = 2e5 + 10;
int T;
int n;
ll a[N], dp[N];
ll ans = 0;
int main() {
cin >> T;
while (T--) {
scanf("%d", &n);
ll mx = 0, mi = 1e9 + 10;
for (int i=1; i<=n; i++) {
scanf("%lld", &a[i]);
mx = max (mx, a[i]);
mi = min (mi, a[i]);
}
sort(a+1, a+n+1);
ans = (mx-mi) * 2;
dp[0] = 0;
for (int i=1; i<=n; i++) dp[i] = dp[i-1];
ll cmp = 0;
for (int i=1; i<=n; i++) {
dp[i] = max(dp[i-1], dp[i]);
if ((i-1)%2 == 0) {
if (i>=4 && (n-i+1)>=4) {
dp[i] = max (dp[i], dp[i-4] + a[i] - a[i-1]);
}
if (i>=6 && (n-i+1)>=4) {
dp[i] = max (dp[i], dp[i-6] + a[i] - a[i-1]);
}
}
}
printf("%lld\n", ans-dp[n]*2);
}
return 0;
}