CodeForces 868F Yet Another Minimization Problem(决策单调性优化 + 分治)
Posted zjp-shadow
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CodeForces 868F Yet Another Minimization Problem(决策单调性优化 + 分治)相关的知识,希望对你有一定的参考价值。
题意
给定一个序列 ({a_1, a_2, cdots, a_n}),要把它分成恰好 (k) 个连续子序列。
每个连续子序列的费用是其中相同元素的对数,求所有划分中的费用之和的最小值。
(2 le n le 10^5, 2 le k le min(n, 20), 1 le a_i le n)
题解
(k) 比较小,可以先考虑一个暴力 (dp) 。
令 (dp_{k, i}) 为前 (i) 个数划分成 (k) 段所需要的最小花费。
那么转移如下
[
dp_{k, i} = min_{j le i} dp_{k - 1, j - 1} + w_{j, i}
]
其中 (w_{j, i}) 为 (j sim i) 这段划分出来需要的花费,也就是 ([j, i]) 区间内相同元素对数。
暴力做是 (O(n^2 k)) 的,无法通过。
说到最优区间划分,我就想起了决策单调性,今年下半年
至于为什么满足决策单调?考虑证明 (mathrm{1D/1D}) 上的 四边形不等式。具体证明可以参考此处。
我们现在只有一个问题了, 就是如何快速求出 (w_{j, i}) 。
可以考虑把序列分块,然后预处理块到块的答案以及点到一个块的答案,然后再算算边角。
然后这个配合 二分+单调栈 可以做到 (O(nk sqrt n log n)) ,还是过不去。
对于这种分层 (dp) 来说,分治的复杂度就可以正确,因为每次不需要先分治左区间再算右区间,可以扫完整个区间得到 (mid) 的最优决策点,然后就可以把 ([l, mid)) 和 ((mid, r]) 的决策点分开了。
这样单次求解的话,每层是 (O(n)) 的,那么复杂度是 (O(n log n)) 的。
然后此时我们就可以很好的算 (w_{j, i}) 了,要怎么算呢?
可以暴力一点做,考虑类似莫队那样维护当前计算区间的 ([l, r]) ,然后看接下来要算的 ([l', r']) 的相对位置,就可以得到相应的区间的花费了。
复杂度?其实是对的。具体原因可以参考非指针移动的那种做法,每次只会移动当前区间长度的指针。
这个其实是一样的,因为每次需要利用的相邻两个区间是一样的,这种移动方法的复杂度是平面上两点的曼哈顿距离,显然不会更劣。
那么最后复杂度就是 (O(nk log n)) 的,似乎我的写法跑的挺快?
代码
很好写啊qwq
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
typedef long long ll;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("F.in", "r", stdin);
freopen ("F.out", "w", stdout);
#endif
}
const int N = 2e5 + 1e3;
int n, k, a[N], times[N];
int l, r; ll res, dp[25][N];
void Move(int L, int R) {
while (l > L) res += times[a[-- l]] ++;
while (l < L) res -= -- times[a[l ++]];
while (r > R) res -= -- times[a[r --]];
while (r < R) res += times[a[++ r]] ++;
}
void Divide(int k, int l, int r, int dl, int dr) {
if (l > r) return;
int mid = (l + r) >> 1, dmid = dl;
dp[k][mid] = 0x3f3f3f3f3f3f3f3f;
For (i, dl, min(mid, dr)) {
Move(i, mid);
if (chkmin(dp[k][mid], dp[k - 1][i - 1] + res)) dmid = i;
}
Divide(k, l, mid - 1, dl, dmid);
Divide(k, mid + 1, r, dmid, dr);
}
int main () {
File();
n = read(); k = read();
For (i, 1, n) a[i] = read();
For (i, 1, n)
dp[1][i] = (res += times[a[i]] ++);
res = 0; Set(times, 0);
l = 1; r = 0;
For (i, 2, k) Divide(i, 1, n, 1, n);
printf ("%lld
", dp[k][n]);
return 0;
}
以上是关于CodeForces 868F Yet Another Minimization Problem(决策单调性优化 + 分治)的主要内容,如果未能解决你的问题,请参考以下文章