【2020牛客多校】2020牛客暑期多校訓練營(第二場)E-Two Matchings——複雜思維與簡單dp
比賽期間寫博文,隊友我家挖祖墳
主要是我數論只會gcd,所以我只好掛機了
注意本文中的部分字母和原文稍有不同,請注意!
題意
定義序列 \(a\) ,滿足如下要求
- 長度為 \(n\) 的序列 \(a\) 由 \(1, 2, 3... n\) 組成
- \(a_{a_i} = i\)
- \(a_i \neq i\)
定義一個字串的費用為\(\sum_{i=1}^{n}w_i - w_{a_i} / 2\) , \(w\) 為給出的權值陣列
求兩個滿足上述對序列 \(a\) 的描述的序列 \(p, q\),同時還要滿足 \(p_i \neq q_i\) 對於每一個 \(i\) 都成立
則這兩個序列的費用和的最小值是多少
分析
根據條件
- 長度為 \(n\) 的序列 \(a\) 由 \(1, 2, 3... n\) 組成
- \(a_{a_i} = i\)
- \(a_i \neq i\)
可以得到序列是由基礎序列 \(1, 2, 3...n\) 通過進行兩兩對調得到,且每個值進行且只進行一次對調。(這裡就不仔細證明了,應該……在打這個比賽的人應該都能理解吧)
注意,接下來的討論僅討論排序後的下標,即如果寫著 \(1\) 則指代 sort 後的陣列 \(w\) 中最小的值
最小值
首先是最小的值,那很明顯,把 w
陣列排序後,間隔著相減就可以得到,例如下面已經排序後的下標序列:
\[1, 2, 3, 4, 5, 6 \]
我們可以得到其最小的解為
\[(2 - 1) + (4 - 3) + (6 - 5) \]
我們暫時不去處理這個解,保留原樣
次小值
接下來是次優解,首先應當保證其每一位的值不相同
由於我們已經將最小值的組合取完了,則次優解就有了非常多的限制
我們可以“旋轉”這個數列得到
\[2, 3, 4, 5, 6, 1\rightarrow (3 - 2) + (5 - 4) + (6 - 1) \]
把這個“旋轉”暫時稱為 \(6-rotation\),指代 \(6\) 個元素的旋轉
而此時即為次優的解(之一)。
證明
我們以六個數字的數列來證明上述操作
首先用 \(-\) 表示這個值作為其所在的交換中的較小值, \(+\)
例如最小值可以表示為
1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|
- | + | - | + | - | + |
我們並不需要具體考慮哪個值與哪個值交換,因為最終的求和結果是一樣的,即上面的值與下面的符號結合後相加就是最終結果。
除去最小解後,我們只有以下兩種組合方法
下標 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
最小值 | - | + | - | + | - | + |
方案1 | - | - | + | - | + | + |
方案2 | - | - | - | + | + | + |
錯誤方案 | - | - | + | + | - | + |
這裡舉例一個錯誤的方案,雖然看起來此方案是與最小值方案不同,但是注意一下最後兩個值,無論這個錯誤方案怎麼組合,\(5-6\) 必然要發生組合併發生交換,則與最小值的方案出現重複,則不行。
那麼我們比較一下這兩個方案哪個更優
\[\frac{方案1}{方案2} = \frac{abs(-1-2+3-4+5+6)}{abs(-1-2-3+4+5+6)} = \frac{abs(+3-4)}{abs(-3+4)} = \frac{4-3}{4-3} = \frac{1}{1} \]
很明顯,其實哪個方案都是次優的………………這也就是為什麼上面寫著次優的解(之一)的原因。
合併最小值和次小值
我們將兩個解相加發現最終結果為
\[[(2 - 1) + (4 - 3) + (6 - 5)] + [(3 - 2) + (5 - 4) + (6 - 1)] =2 * (6 - 1) \]
長度不及 \(6\) 的時候
而對於長度僅為 \(4\) 的串,只能 \(4-rotation\) ,即
\[1, 2, 3, 4 \rightarrow (4-rotation)\rightarrow 2, 3, 4, 1 \rightarrow (3 - 2) + (4 - 1) \]
此時的最終結果為(過程忽略)
\[2 * (4 - 1) \]
長度為\(8\)的時候
那麼我們再往長度增長的方向考慮,當 \(n = 8\) 時,我們有兩個方案,
- 兩個 \(4-rotation\) ( \(1234\) 和 \(5678\) )來旋轉它
- 兩個 \(4-rotation\) ( \(1278\) 和 \(3456\) )來旋轉它
- 一個 \(8-rotation\) 來旋轉它
注意,此題是不存在 \(2-rotation\) 的,因為這毫無意義,所以 \(n = 8\) 時,沒有一個 \(6-rotation\) 和一個 \(2-rotation\) 這樣的組合。
先比較一下兩個 \(4-rotation\):
\[\frac{方案1}{方案2} = \frac{2 * [(4 - 1) + (8 - 5)]}{2 * [(8 - 1) + (6 - 3)]} = \frac{12}{20} \]
我們選擇使用方案 \(1\)
接下來是方案 \(1\) 和方案 \(3\) 的比較
\[\frac{方案1}{方案3} = \frac{2 * [(4 - 1) + (8 - 5)]}{2 * [(8 - 1)]} = \frac{12}{14} \]
此時證明得到方案 \(1\) 在三個方案內最優,此時 \(n =8\) 時的答案為:
\[2 * [(4 - 1) + (8 - 5)] \]
同時我們得到了一個結論:僅存在 \(4-rotation\) 和 \(6-rotation\) 兩種旋轉,如果存在 \(8-rotation\) 則可以將此 \(8-rotation\) 分解為兩個 \(4-rotation\) 可以更優。
長度更長的時候
當 \(n \geq 10\) 時,即可以將整個串分解成多個 \(4-rotation\) 和多個 \(6-rotation\) 組成。
那麼得到了 \(dp\) 的遞推公式:dp[i] = min(dp[i - 4] + v[i - 1] - v[i - 4], dp[i - 6] + v[i - 1] - v[i - 6])
注意 \(dp\) 的初始值有三個:\(n = 4, n = 6, n = 8 \space (防止n = 8的時候出現2-rotation)\)
AC code
#include <bits/stdc++.h>
using namespace std;
long long dp[200100];
void solve() {
int T;
cin >> T;
for (int ts = 0; ts < T; ++ts) {
int n;
cin >> n;
vector<long long> v;
for (int i = 0; i < n; ++i) {
long long tmp;
cin >> tmp;
v.push_back(tmp);
}
sort(v.begin(), v.end());
dp[0] = 0;
dp[4] = v[3] - v[0];
dp[6] = v[5] - v[0];
dp[8] = v[7] - v[4] + dp[4];
for (int i = 10; i <= n; i += 2)
dp[i] = min(dp[i - 4] + v[i - 1] - v[i - 4], dp[i - 6] + v[i - 1] - v[i - 6]);
cout << dp[n] * 2 << endl;
}
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
int test_index_for_debug = 1;
char acm_local_for_debug;
while (cin >> acm_local_for_debug) {
if (acm_local_for_debug == '$') exit(0);
cin.putback(acm_local_for_debug);
if (test_index_for_debug > 20) {
throw runtime_error("Check the stdin!!!");
}
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
}
#else
solve();
#endif
return 0;
}