slope tricl

一个奇奇怪怪的包

额,直接从题开始讲吧。

P4597 序列 sequence

这道题,先说暴力

暴力

考虑 \(dp\),设 \(f_{ij}\) 表示考虑到第 \(i\) 个数,当前数改为 \(j\),那么很明显,我们可以通过枚举上一个 \(k\),来形成一个 \(O(n^3)\)\(dp\)

优化 1

我们发现,由于是单调不减,所以 \(k\) 是有取值范围的,并且这个取值范围是连续的,于是我们设 \(g_{ij}\) 表示在 \(f_{ik}\)\(k\) 小于 \(j\) 的所有的 \(f_{ik}\) 的最小值,所以转移方程为:

\(f_{ij} = g_{ij} + |a[i]-j|\)

优化 2

啊,终于进正题了。

考虑将 \(f_i\) 变为一个函数,即对于每一个 \(j\)\(f_i\) 这个函数都有一个值,由于后面的绝对值必然是一个凸函数,而前面的 \(f\) 等都可以从 \(f_0\) 转移,而 \(f_0\) 是条平线(额,这个没必要解释吧,两个 0 相减不会有人不会算吧),所以在累加的过程中 \(f_i\) 必然是个凸函数。

所以我们要求 \(f_i\) 函数中的最低处,即 \(f_{i}\) 这个分段函数中斜率为 1 的时候。

那么同理可以将 \(g_{i}\) 也视为一个函数,那么转移便成了将两函数叠加,如何快速叠加呢?

考虑维护函数中的 \(x\) 最大的一段,记下它的解析式,然后每段记录转折,并记录转折次数。

突然想起,\(f_i\) 为凸函数,我们可将其形状是为一个抛物线,而 \(g_i\) 的形状可视为一个反比例函数(形象的视为,并不是真的),而 \(g_i\) 函数必然会从一个点开是变为一条射线,而这条射线,放到 \(f_i\) 处,便是我们要知道的东西,所以 \(f_i\),OUT!

其实我们也没必要维护 \(g_i\) 的最后的解析式,直接将点记录下来,反正在叠加的时候加的都是一个 \(k = 1 或 -1\) 的东西。

那么我们设 \(h_{i}\)\(f_i(j)=g_i(j)\) 的时候即 \(f_i\) 的最低点。

考虑当新加点比 \(h_{i - 1}\)(说的是值啊) 等于或还大,那么肯定没有 \(h_{i - 1}\) 更优,直接丢进点列以后再说。

如果新加点比 \(h_{i - 1}\) 小,那么由于是比 \(h_{i - 1}\) 更优,所以在加进点列是肯定会放在最前面(即 \(h_i\) 的值为新加点),且由于比 \(h_{i-1}\) 要小,最低的 \(x\) 的必然比 \(h_{i - 1}\) 的要靠前,此时出现了一个折返,便不是最优点了,所以删掉,而此时的最后解析式和第一个转折点都是新加点,需要加两次。

code

#include <iostream>
#include <queue>

using namespace std;

const int MaxN = 5e5 + 10;

int a[MaxN], n;
long long ans;
priority_queue<int> q; // 虽然说这个函数会一点点变小,不过由于哪里的为 k = -1 的,所以反过来就是一点点变大啦!

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    q.push(a[i]); // 不是要加两次吗?我这里投个懒
    if (a[i] < q.top()) {
      ans += q.top() - a[i]; // 记得要算贡献,只有将 a[i] 提上来才能保证单调不减
      q.pop();
      q.push(a[i]);
    }
  }
  cout << ans << endl;
  return 0;
}

CF713C Sonya and Problem Wihtout a Legend

哇,自己会写模板了,好厉害!结果一看,哇单调递增,吐血三升……

思路

由于我们指挥单调不减,所以可以将单调递增转化为单调不减。

反着来考虑,如果是单调不减,那么会有相同的地方,所以加一个 \(i\) 即可,那么单调递增就反过来减一个 \(i\) 即可,其余一样。

posted @ 2024-07-13 01:28  yabnto  阅读(8)  评论(0编辑  收藏  举报