P4983 忘情 Sol

upd. 2023.3.14 更新了关于单调队列判定条件的一些表述。

首先考虑不限制 m 的怎么做。

式子可以化简为 min{[li,ri][(j=liriaj)+1]2}

考虑 dpi 表示当前选到截止 i 的一段,为 [1,i] 的最小代价。

那么枚举上一个点 1j<i,则 dpi=min{dpj+cost(j+1,i)}

cost(j+1,i) 表示 [j+1,i] 对于答案的贡献,即 [(p=j+1iap)+1]2

si=j=1iaj,拆开式子。

dpi=min{dpj+(sisj)2+2(sisj)+1}

进一步拆开得到

dpi=min{dpj+si2+sj22sisj+2si2sj+1}

看到同时含 ij 项的乘积 sisj,考虑斜率优化。

1j1<j2<in,且 j2 优于 j1

则有

dpj1+si2+sj122sisj1+2si2sj1+1dpj2+si2+sj222sisj2+2si2sj2+1

dpj1+sj122sisj12sj1dpj2+sj222sisj22sj2

把含 i 的项提到左边,其余提到右边。

2si(sj2sj1)(dpj2+sj222sj2)(dpj1+sj122sj1)

同时除以 (sj2sj1),得

2si(dpj2+sj222sj2)(dpj1+sj122sj1)sj2sj1

这就是 j2>j1 且优于 j1 的条件。

发现右边一个与 sj2 有关,一个与 sj1 有关,可以看作一个函数,维护一个单调队列即可。

接下来,我们看到 m 限制,很自然地想到 wqs 二分。

f(i) 表示强制选 i 个区间的最小代价,发现 f(i) 单调递减且 f(i)f(i+1)>f(i+1)f(i+2)

因为如果 f(i)f(i+1)<f(i+1)f(i+2),就有把 f(i+1)f(i+2) 的区间变化换到 f(i)f(i+1) 处才满足f(i) 为强制选 i 个区间的最小代价的定义。

我们确定了这玩意儿可以用 wqs 二分做,且这个图像呈下凸壳的左半部分(可画图找规律)。

也就是其斜率左侧小,右侧大。

我们二分到一个斜率,就可以进行一次 DP,来获得当前的最小代价(即截距)。

加上 m×mid 就可以获得真实的代价。

(当然,对于和 m 不为同一斜率的点无法还原其答案,但是由于其答案最后肯定会被更新为与 m 同一条直线上的答案,所以没关系)

除此之外,由于当前要在答案最小的情况下,选择区间最少的方案。

那么考虑到当前答案从更早的地方转移过来一定更优,那么加强判定条件,改成 >

没了。

#include <bits/stdc++.h>
#define int long long
#define fi first
#define se second
using namespace std;

const int N = 1e5 + 10;
int n, m, a[N], sum[N], q[N];
pair <int, int> dp[N];

inline int y(int j1, int j2) {
  return (sum[j2] * sum[j2] - 2 * sum[j2] + dp[j2].fi) -
  (sum[j1] * sum[j1] - 2 * sum[j1] + dp[j1].fi);
}

inline int x(int j1, int j2) {
  return sum[j2] - sum[j1];
}

inline pair <int, int> check(int mid) {
  int l = 1, r = 1;
  for (int i = 1; i <= n; ++i) {
    while (l < r && 2 * sum[i] * x(q[l], q[l + 1]) > y(q[l], q[l + 1])) ++l;
    dp[i].fi = dp[q[l]].fi + (sum[i] - sum[q[l]] + 1) * (sum[i] - sum[q[l]] + 1) - mid, dp[i].se = dp[q[l]].se + 1;
    while (l < r && y(q[r], i) * x(q[r - 1], q[r]) <= y(q[r - 1], q[r]) * x(q[r], i)) --r; q[++r] = i;
  }
  return dp[n];
}

signed main() {
  ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
  cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i];
  int l = -1e18, r = 0, res = 0;
  while (l <= r) {
    int mid = (l + r) >> 1; auto tmp = check(mid);
    if (tmp.se <= m) res = tmp.fi + m * mid, l = mid + 1;
    else r = mid - 1;
  }
  cout << res << endl;
  return 0;
}
posted @   MistZero  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示