「题解」洛谷 P4597 序列sequence

「题解」洛谷 P4597 序列sequence

2023.1.30 upd:因为忘了 slope trick 想再学学,重新看了一下这个题解,发现是完全的误人子弟。只是一个很低的角度的理解方法,还是学习zxy的教程


感觉十分厉害的题,记录一下(

有个很显然的 O(nmaxa) 的 dp,设 fi,jai 变为 j,考虑原序列 [1,i] 的最小代价。

显然有:

fi,j=minkjfi1,k+|aij|

结论1:fi,j 关于 j 的点相邻两个连起来是一个下凸壳。

证明:

f1,j=|a1j|,是一个绝对值函数,顶点在 a1

考虑 fi1fi 会发生怎样的变化。

分类讨论一下,具体可以看这里的证明,我认为写得非常地好,无需多补充,不过请务必看懂有关下凸壳的证明,后面的做法会涉及到加入 ai 对下凸壳线段的影响。

下凸壳的斜率肯定是递增的。先钦定线段的斜率是不断 +1 的,如果中间出现了断层的现象就当作这个断的地方有长度为 0 的线段,这些线段的斜率补充了中间断层的地方。

再设 opifi 的斜率为 0 的那一条线段的左端点,也是斜率为 1 的那一条线段的右端点。

我们需要明确一点,最终答案是 fn,opn,换句话说,我们只关心 fn,opn纵坐标,不关心 opn 具体是几,因为问题只要求输出最小的代价。

现在可以完全不管斜率 >1 的线段了,它们怎么变和求答案没有关系。


考虑维护这些斜率 0 的线段,设计一个可重元素的由大到小的优先队列(或者说大根堆),其元素为线段的右端点,一个右端点可以重复多次,其线段的斜率为其需要完全 pop 出的次数的相反数。

举个栗子,大根堆为 {3,2,2,1,1},那么右端点为 2 的线段的斜率为 3,因为弹出全部的 2 需要把一个 3,两个 2 全部弹出。

现在惊奇地发现,这个优先队列恰好对上了我们前面钦定的斜率不断 +1 递增"的"暴论"。

关于要用到的结论上面给出的链接已经证明,我现在重新叙述一遍。

  1. 线段单降且在 ai 左侧,斜率 1(其斜率的数值减去 1);

  2. 线段单降且在 ai 右侧,斜率 +1

  3. 线段不单降且在 ai 左侧,斜率变为 1

  4. 线段不单降且在 ai 右侧,斜率变为 1

考虑从 fi1fi,一个 ai 会对答案造成怎样的影响。

设答案为 ans,也就是考虑 i 增加时 ans 如何改变。

Case1: aiopi1

这个时候 ai 前面的线段斜率都 <0 了,而 opi1 又是 fi1,j 取到最小值时的 j,又在 ai 前面,所以可以从 opi1 转移而来。答案变为 fi,ai 处的取值即为 fi1,opi1+|aiai|ans 不变。

由于前面的线段斜率都会 1,要维护堆的意义,就把 ai push 进堆中,这样堆中所有元素其 pop 完的次数都 +1,则斜率就 1

Case2: ai<opi1

首先考虑答案改变成什么样子,注意到新的决策点 opi 即为图中红点和 opi1 之间的斜率变为了 0,所以 fi,opi=fi,opi1=fi1,opi1+opi1ai。例如图中,这个时候每部分的线段的斜率由图中黑色的变成红色的,中间有一段 3,1 中间断开了,少了 2,换句话说,由于我们 push 进去了 ai,还想要维护堆的意义,就要把 opi1 弹出,再把 ai 压进去,使其拥有斜率为 2 的段。

我们要维护堆能够以 1 递增实际上是让每次 ai<top 的时候保证加进去 aitop 作为右端点代表的线段斜率变为了 1,保证它一定是需要弹出的点。所以多 push 进去是对的。

图中为一个栗子,值得注意的是两条线段之间的长度可能为 0,但这不影响答案。

综上所述,我们得到了一个很简洁的代码来完成这个过程。

时间复杂度 O(nlogn)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
template <typename T> T Max(T x, T y) { return x > y ? x : y; }
template <typename T> T Min(T x, T y) { return x < y ? x : y; }
template <typename T>
T &read(T &r) {
	r = 0; bool w = 0; char ch = getchar();
	while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
	while(ch >= '0' && ch <= '9') r = (r << 3) + (r << 1) + (ch ^ 48), ch = getchar();
	return r = w ? -r : r;
}
const int N = 500010;
int n, a;
std::priority_queue<int>q;
long long ans;
signed main() {
	read(n);
	for(int i = 1; i <= n; ++i) {
		read(a); q.push(a);
		if(a < q.top()) {
			ans += q.top() - a;
			q.pop();
			q.push(a);
		}
	}
	printf("%lld\n", ans);
	return 0;
}

感谢slyz的两位学长的指点以及 Mr_Wu 的题解

笔者才疏学浅,如有错误欢迎指出,如有疑惑也欢迎提出。

posted @   do_while_true  阅读(177)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?

This blog has running: 1845 days 1 hours 33 minutes 16 seconds

点击右上角即可分享
微信分享提示