牛客暑期多校第三场 E-Two Matchings
Two Matchings
https://ac.nowcoder.com/acm/contest/5668/E
E-Two Mathcings
题意
给一个序列。
要找到两种不同的整个序列的两两匹配,使得所有两两匹配的差的和最小,输出这个和。
思路
经过几次思考和画画发现,其实最佳的匹配策略只有把原来的序列升序排序后,分为长度为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最小的匹配满足:总花费 = 。
回到原来的序列,因为n为偶数,假设原来的整个序列为一起进行两两匹配,那它原始的花费在理想情况下应该也为。
但是由于我们发现可以把原序列长度拆成,即a个长度为4的区间和b个长度为6的区间。而相比原始的cost,减少的就是每个区间裂开的点的值。那我们用dp数组状态转移一下:。
最终答案就是。
排个序,转移下状态输出结果。
代码
/* * @ 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; }