题意
传送门 AtCoder ABC 328G Cut and Reorder
题解
假设答案对应的 a a a 下标 0 , 1 , ⋯ , n − 1 0,1,\cdots,n - 1 0,1,⋯,n−1 经过操作 1 变换为排列 p 0 , p 1 , ⋯ , p n − 1 p_{0},p_{1},\cdots,p_{n-1} p0,p1,⋯,pn−1,则对于满足 p i − 1 ≠ p i , 0 < i p_{i-1}\neq p_{i},0<i pi−1=pi,0<i 的位置,都至少要分割一次,且仅需这样的分割就可以得到 a p 0 , a p 1 , ⋯ , a p n − 1 a_{p_{0}},a_{p_{1}},\cdots,a_{p_{n-1}} ap0,ap1,⋯,apn−1。则状态压缩优化 p p p 的枚举即可。
不考虑分割贡献,则需要维护两个变量:集合 A = { a p 0 , a p 1 , ⋯ , a p m } \mathcal{A}=\{a_{p_{0}}, a_{p_{1}}, \cdots,a_{p_{m}}\} A={ap0,ap1,⋯,apm},以及集合匹配的 b b b 的前缀的长度 m m m。实际上,只需要维护前者,因为 ∣ A ∣ = m \lvert\mathcal{A}\rvert = m ∣A∣=m。
容易想到两类计算分割贡献的方案。一类是新增状态 a p i − 1 a_{p_{i-1}} api−1,转移时可以直接判断 a p i − 1 ≠ a p i a_{p_{i-1}}\neq a_{p_{i}} api−1=api,时间复杂度 O ( n 2 log n ) O(n^2\log n) O(n2logn)。另一种方案是枚举分割的段,此时每一个连续的段(除了起始段)操作 1 贡献是 c c c; A \mathcal{A} A 状态数为 2 n 2^n 2n,对于每一个状态,可能的段规模为 O ( n 2 ) O(n^2) O(n2),时间复杂度也是 O ( n 2 log n ) O(n^2\log n) O(n2logn);实际上,这是一个宽松的界,对于每一个段的长度 k k k,实际上对应的状态数为 2 n − k 2^{n-k} 2n−k,则一个更紧的界为 O ( ∑ k 2 n − k ) = O ( n log n ) O(\sum k2^{n-k})=O(n\log n) O(∑k2n−k)=O(nlogn)。
#include <bits/stdc++.h>
using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n;cin >> n;long long c;cin >> c;vector<long long> a(n), b(n);for (int i = 0; i < n; ++i) {cin >> a[i];}for (int i = 0; i < n; ++i) {cin >> b[i];}auto get_min = [&](long long &x, long long y) {x = min(x, y);};const long long inf = 1e18;vector<long long> dp(1 << n, inf);dp[0] = 0;for (int i = 0; i < 1 << n; ++i) {int bi = __builtin_popcount(i);for (int l = 0, r = 0; l < n; l = r) {if (i >> l & 1) {r = l + 1;continue;}while (r < n && (~i >> r & 1)) {r += 1;}for (int ai = l; ai < r; ++ai) {int st = 0;long long add = 0;for (int d = 1; ai + d <= r; ++d) {st |= 1 << (ai + d - 1);add += abs(a[ai + d - 1] - b[bi + d - 1]);long long split = bi == 0 ? 0 : c;get_min(dp[i | st], dp[i] + add + split);}}}}cout << dp[(1 << n) - 1] << '\n';return 0;
}