CodeForces 868F Yet Another Minimization Problem(决策单调性优化 + 分治)

题意

给定一个序列 {a1,a2,,an},要把它分成恰好 k 个连续子序列。

每个连续子序列的费用是其中相同元素的对数,求所有划分中的费用之和的最小值。

2n105,2kmin(n,20),1ain

题解

k 比较小,可以先考虑一个暴力 dp

dpk,i 为前 i 个数划分成 k 段所需要的最小花费。

那么转移如下

dpk,i=minjidpk1,j1+wj,i

其中 wj,iji 这段划分出来需要的花费,也就是 [j,i] 区间内相同元素对数。

暴力做是 O(n2k) 的,无法通过。

说到最优区间划分,我就想起了决策单调性,今年下半年

至于为什么满足决策单调?考虑证明 1D/1D 上的 四边形不等式。具体证明可以参考此处

我们现在只有一个问题了, 就是如何快速求出 wj,i

可以考虑把序列分块,然后预处理块到块的答案以及点到一个块的答案,然后再算算边角。

然后这个配合 二分+单调栈 可以做到 O(nknlogn) ,还是过不去。

对于这种分层 dp 来说,分治的复杂度就可以正确,因为每次不需要先分治左区间再算右区间,可以扫完整个区间得到 mid 的最优决策点,然后就可以把 [l,mid)(mid,r] 的决策点分开了。

这样单次求解的话,每层是 O(n) 的,那么复杂度是 O(nlogn) 的。

然后此时我们就可以很好的算 wj,i 了,要怎么算呢?

可以暴力一点做,考虑类似莫队那样维护当前计算区间的 [l,r] ,然后看接下来要算的 [l,r] 的相对位置,就可以得到相应的区间的花费了。

复杂度?其实是对的。具体原因可以参考非指针移动的那种做法,每次只会移动当前区间长度的指针。

这个其实是一样的,因为每次需要利用的相邻两个区间是一样的,这种移动方法的复杂度是平面上两点的曼哈顿距离,显然不会更劣。

那么最后复杂度就是 O(nklogn) 的,似乎我的写法跑的挺快?

代码

很好写啊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\n", dp[k][n]); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10278994.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(470)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示