Codeforces#721 E
题目链接
题意:将一个序列分成\(k\)段,求出这\(k\)段中\(cost(t) = \sum\limits_{x \in set(t)} last(x) - first(x)\)的大小,也就是求出区间内最后一次出现的数和第一次出现的数之间的差值。
思路:
对于求解这个式子的最小值,考虑用\(dp\)来求解,\(dp\left[i\right]\left[j\right]\)表示在下标为\(j\)的位置之前分成了\(i\)段,从而有了\(dp\left[i\right]\left[j\right] = \min\limits_{k < j} \{ dp[\left[i - 1\right]\left[k\right] + w\left[k + 1\right]\left[j\right]\}\),如果暴力的去求解这个式子的话会因为后面\(w\left[k + 1\right]\left[j\right]\)这个式子超时,所以重点就是要优化这个式子。
\(last(x) - first(x)\)可以拆成\(last(x) - prev(x) + \left(prev\left(x\right) - first\left(x\right)\right)\),也就是我们去求这个式子的时候可以用前缀和的操作来进行优化。而每一次求解\(\min\)的操作的时候,同时需要找到区间最小值来是的前缀和最小,那么就可以考虑用线段树来维护。
int n, m;
std::cin >> n >> m;
std::vector<int> pre(n + 1), last(n + 1), a(n + 1);
for (int i = 1; i <= n; i++ ) {
std::cin >> a[i];
last[i] = pre[a[i]];
pre[a[i]] = i;
}
std::vector<int> dp(n + 1, 2E9);
dp[0] = 0;
for (int i = 0; i < m; i ++ ) {
Seg SGT(n + 1);
for (int i = 0; i <= n; i++ ) {
SGT.change(1, 0, n, i, i, dp[i]);
}
for (int i = 1; i <= n; i++ ) {
if (last[i]) SGT.change(1, 0, n, 0, last[i] - 1, i - last[i]);
dp[i] = SGT.query(1, 0, n, 0, i - 1);
}
}
std::cout << dp[n] << "\n";

浙公网安备 33010602011771号