[APIO 2014] 序列分割

本文详细解析了一道关于斜率优化DP算法的题目,通过分析题目特点,提出了使用fi,j表示切i次,前j个数的最大收益的策略,并介绍了如何通过斜率优化来解决该问题。文章还提供了具体的代码实现,包括滚动数组优化空间复杂度的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[题目链接]

         https://www.lydsy.com/JudgeOnline/problem.php?id=3675

[算法]

         首先 , 我们发现将一段序列切成若干段所获得的收益与顺序无关

         于是我们可以用fi,j表示切i次 , 前j个数的最大收益

         令sumi表示ai的前缀和

         显然 , fi,j = max{ fi-1,k + sumk * (sumj - sumk) }

         斜率优化即可

         此题内存限制较紧 , 可以使用滚动数组优化空间复杂度

         时间复杂度 : O(NK)

[代码]

       

#include<bits/stdc++.h>
using namespace std;
#define MAXN 100010
#define MAXK 210
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;

int n , k , l , r;
ll X[MAXN] , Y[MAXN] , sum[MAXN] , f[2][MAXN];
int a[MAXN] , q[MAXN] , last[MAXK][MAXN];

template <typename T> inline void chkmax(T &x,T y) { x = max(x,y); }
template <typename T> inline void chkmin(T &x,T y) { x = min(x,y); }
template <typename T> inline void read(T &x)
{
    T f = 1; x = 0;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
    x *= f;
}
int main()
{
        
        read(n); read(k);
        for (int i = 1; i <= n; i++)
        {
                read(a[i]);
                sum[i] = sum[i - 1] + a[i];
        }
        for (int i = 1; i <= k; i++)
        {
                int now = i & 1 , pre = now ^ 1;
                f[now][q[l = r = 1] = 0] = 0;
                for (int j = 1; j <= n; j++)
                {
                        while (l < r && Y[q[l + 1]] - Y[q[l]] >= -sum[j] * (X[q[l + 1]] - X[q[l]])) ++l;
                        f[now][j] = Y[q[l]] + X[q[l]] * sum[j];
                        X[j] = sum[j];
                        Y[j] = f[pre][j] - sum[j] * sum[j];
                        last[i][j] = q[l];
                        while (l < r && (Y[j] - Y[q[r]]) * (X[q[r]] - X[q[r - 1]]) >= (Y[q[r]] - Y[q[r - 1]]) * (X[j] - X[q[r]])) --r;
                        q[++r] = j; 
                }        
        }
        printf("%lld\n" , f[k & 1][n]);
        int now = n , s = k;
        vector< int > ans;
        while (now > 0)
        {
                now = last[s][now];
                if (now) ans.push_back(now);
                --s;
        }
        reverse(ans.begin() , ans.end());
    
        // 输出方案...

        return 0;
    
}

 

转载于:https://www.cnblogs.com/evenbao/p/10354216.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值