关于 F 的斜率优化 DP

CSDN版本

把 sum 初始化写错了找了恁久错。。。

Solution

把题中 的定义改为 ,即令 ,如下式所示,其中 ,表示 的前缀和

再令 ,化简得

表示将前 个数分为 段的最小值。

考虑其转移

根据斜率优化 ,我们将纯变量放到左边称为 ,将带有“临时常量”系数的放到右边称为 ,最终得到(这里默认是依赖于上一维,所以省略

上式中,有如下对应关系

对于 这条直线,我们最终要的就是截距 最小,而且这里随着 增加, 严格增加,所以维护一个关于 点对的下凸包即可。

对于 ,从下往上逼近凸包的直线斜率为 ,当这条直线第一次碰到凸包上的点,截距就是最小的。

我们可以用单调队列维护这个凸包,即对于凸包最左侧的两个相邻点产生直线的斜率 ,若 ,就将凸包最左侧的点弹出,这样能保证队头一定是最优的。

而要把第 个点插入队尾,就要求 与凸包最右侧的点产生的斜率 ,严格大于凸包最右侧两点产生的斜率 (这是根据下凸包的定义来的),如下图所示(图片来源

alt

在这里,斜率 的计算公式为

最后一点是,每次更新的时候, 的范围其实是 ,因为少于 个数一定不可能被分为 段,这样可以节省一半计算量。

时间复杂度

C++ Code

#include <bits/stdc++.h>

using i64 = int64_t;
using u64 = uint64_t;
using f64 = double_t;
using i128 = __int128_t;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    std::cout << std::fixed << std::setprecision(12);
	
    int n, m, k;
    std::cin >> n >> m >> k;

    std::vector<int> a(n);
    for (int i = 0; i < n; i++) {
    	std::cin >> a[i];
    }
    std::vector<int> s(n + 1);
    for (int i = 0; i < n; i++) {
    	s[i + 1] = s[i] + a[i];
    }
    std::vector<int> x(n + 1);
    for (int i = 1; i <= n; i++) {
    	x[i] = s[i] + i * (i + 1) / 2;
    }

    std::vector<int> q(n + 1);
    std::vector<i64> dp(n + 1), ndp(n + 1);
    for (int i = 1; i <= n; i++) {
    	dp[i] = 1LL * (x[i] + k) * (x[i] + k);
    }
    std::vector<i64> y(n + 1);
    for (int i = 1; i <= n; i++) {
        y[i] = dp[i] + 1LL * x[i] * x[i];
    }
    for (int c = 2; c <= m; c++) {
    	int hh = 0, tt = 0;
        q[tt++] = c - 1;
    	for (int i = c; i <= n; i++) {
            int coef = x[i] + k;
    		while (hh + 1 < tt and (y[q[hh + 1]] - y[q[hh]]) <= (x[q[hh + 1]] - x[q[hh]]) * 2LL * coef) {
    			hh++;
    		}
    		int j = q[hh];
    		ndp[i] = dp[j] + 1LL * coef * coef + 1LL * x[j] * x[j] - 2LL * coef * x[j];
    		while (hh + 1 < tt and i128(y[i] - y[q[tt - 1]]) * (x[q[tt - 1]] - x[q[tt - 2]]) <= i128(y[q[tt - 1]] - y[q[tt - 2]]) * (x[i] - x[q[tt - 1]])) {
    			--tt;
    		}
    		q[tt++] = i;
    	}
    	for (int i = c; i <= n; i++) {
    		y[i] = ndp[i] + 1LL * x[i] * x[i];
    	}
    	dp.swap(ndp);
    }
    std::cout << dp[n] << "\n";

    return 0;
}
全部评论
佬!
点赞 回复 分享
发布于 2024-07-02 01:23 四川

相关推荐

04-18 00:32
已编辑
中南大学 Java
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务