Codeforces 2153D Not Alone 题解 [ 绿 ] [ 线性 DP ] [ 分类讨论 ]
Not Alone:唐题。
容易将题意转化为:环上的每一个颜色段长度都 \(\ge 2\),求最小操作数。
再考虑一个 \(O(n^2\log n)\) 的暴力,定义 \(dp_i\) 表示以 \(i\) 结尾的最小操作数,然后枚举前一个转移点 \(j\)。继续考虑如何计算 \([j + 1, i]\) 的最小操作数,容易注意到这是个经典的绝对值不等式,于是取中位数即可。
感性地发现一大段相等的数不如拆成几个小段的区间。于是从 \(n = 2, 3,4\) 的情况入手,发现:
- \(n = 2\) 时,只能全部相等。
- \(n = 3\) 时,只能全部相等。
- \(n = 4\) 时,\([a_1, a_2, a_3, a_4]\) 分成 \([a_1, a_2],[a_3, a_4]\) 比直接不分段更优秀,因为分段时的代价就是 \(|a_2 - a_1| + |a_4 - a_3|\),不分段的时候,分类讨论中位数的位置发现最小值才是 \(|a_2 - a_1| + |a_4 - a_3|\),甚至有可能比它更大。因此不分段一定不优。
- \(n = 5, 6, 7, \cdots\) 时,类似 \(n = 4\),考虑数学归纳法,发现分成若干个长度为 \(2, 3\) 的段一定最优。
于是线性 DP 求解最小方案即可。
拓展到环上,破环为链复杂度会爆炸,所以只需要分类讨论首尾相接处的情况即可。详见代码,时间复杂度 \(O(n)\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 400005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll n, a[N], ans, res, dp[N];
ll work(int l, int r)
{
dp[l - 1] = 0;
for(int i = l; i <= r; i++)
{
dp[i] = inf;
if(i == l) continue;
// 2
dp[i] = min(dp[i], dp[i - 2] + abs(a[i] - a[i - 1]));
// 3
if(i == l + 1) continue;
int mx = max(a[i], max(a[i - 1], a[i - 2])), mn = min(a[i], min(a[i - 1], a[i - 2]));
int md = (a[i] ^ a[i - 1] ^ a[i - 2] ^ mx ^ mn);
dp[i] = min(dp[i], dp[i - 3] + mx - md + md - mn);
}
return dp[r];
}
void solve()
{
cin >> n;
ans = inf;
for(int i = 1; i <= n; i++)
cin >> a[i];
// 0 + 0
ans = min(ans, work(1, n));
// 1 + 1
if(n > 3)
ans = min(ans, work(2, n - 1) + abs(a[1] - a[n]));
// 1 + 2
if(n > 4)
{
int mx = max(a[1], max(a[n], a[n - 1])), mn = min(a[1], min(a[n - 1], a[n]));
int md = (a[1] ^ a[n - 1] ^ a[n] ^ mx ^ mn);
ans = min(ans, work(2, n - 2) + mx - md + md - mn);
}
// 2 + 1
if(n > 4)
{
int mx = max(a[1], max(a[n], a[2])), mn = min(a[1], min(a[2], a[n]));
int md = (a[1] ^ a[2] ^ a[n] ^ mx ^ mn);
ans = min(ans, work(3, n - 1) + mx - md + md - mn);
}
cout << ans << "\n";
}
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--) solve();
return 0;
}