洛谷 P3648 [APIO2014]序列分割 斜率优化dp

本文深入探讨了一个关于非负整数序列分割的游戏算法,旨在通过特定操作最大化总得分。文章详细解释了游戏规则,提供了样例操作流程,并分享了一种有效的动态规划解法,包括状态转移方程和代码实现。

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

题目描述
你正在玩一个关于长度为 nnn 的非负整数序列的游戏。这个游戏中你需要把序列分成 k+1k + 1k+1 个非空的块。为了得到 k+1k+1k+1 块,你需要重复下面的操作 kkk 次:
选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。

输入输出格式

输入格式:
第一行包含两个整数 nnnkkk。保证 k+1≤nk+1≤nk+1n

第二行包含 nnn 个非负整数 a1,a2,⋯ ,ana_1, a_2, \cdots, a_na1,a2,,an(0≤ai≤104)(0≤a_i≤10^4)(0ai104),表示前文所述的序列。

输出格式:
第一行输出你能获得的最大总得分。
第二行输出 kkk 个介于 111n−1n−1n1 之间的整数,表示为了使得总得分最大,你每次操作中分开两个块的位置。第 iii 个整数 sis_isi 表示第 iii 次操作将在 sis_isisi+1s_{i + 1}si+1之间把块分开。

如果有多种方案使得总得分最大,输出任意一种方案即可。

输入输出样例

输入样例#1:
7 3
4 1 3 4 0 2 3
输出样例#1:
108
1 3 5
说明

你可以通过下面这些操作获得 108108108 分:

初始时你有一块 (4,1,3,4,0,2,3)(4, 1, 3, 4, 0, 2, 3)(4,1,3,4,0,2,3)。在第 111 个元素后面分开,获得 4×(1+3+4+0+2+3)=524 \times (1 + 3 + 4 + 0 + 2 + 3) = 524×(1+3+4+0+2+3)=52分。

你现在有两块 (4),(1,3,4,0,2,3)(4), (1, 3, 4, 0, 2, 3)(4),(1,3,4,0,2,3)。在第 333 个元素后面分开,获得 (1+3)×(4+0+2+3)=36(1 + 3) \times (4 + 0 + 2 + 3) = 36(1+3)×(4+0+2+3)=36分。

你现在有三块 (4),(1,3),(4,0,2,3)(4), (1, 3), (4, 0, 2, 3)(4),(1,3),(4,0,2,3)。在第 555 个元素后面分开,获得 (4+0)×(2+3)=20(4 + 0) \times (2 + 3) = 20(4+0)×(2+3)=20 分。

所以,经过这些操作后你可以获得四块 (4),(1,3),(4,0),(2,3)(4), (1, 3), (4, 0), (2, 3)(4),(1,3),(4,0),(2,3) 并获得 52+36+20=10852 + 36 + 20 = 10852+36+20=108 分。

限制与约定

第一个子任务共 11 分,满足 1≤k&lt;n≤101≤k&lt;n≤101k<n10

第二个子任务共 11 分,满足 1≤k&lt;n≤501≤k&lt;n≤501k<n50

第三个子任务共 11 分,满足 1≤k&lt;n≤2001≤k&lt;n≤2001k<n200

第四个子任务共 17 分,满足 2≤n≤1000,1≤k≤min(n−1,200)2≤n≤1000,1≤k≤min(n−1,200)2n1000,1kmin(n1,200)

第五个子任务共 21 分,满足 2≤n≤10000,1≤k≤min(n−1,200)2≤n≤10000,1≤k≤min(n−1,200)2n10000,1kmin(n1,200)

第六个子任务共 29 分,满足 2≤n≤100000,1≤k≤min(n−1,200)2≤n≤100000,1≤k≤min(n−1,200)2n100000,1kmin(n1,200)

分析:
对于相同位置的切法,不同的顺序切不影响答案。
显然(a+b)c+ab=a(b+c)+bc(a+b)c+ab=a(b+c)+bc(a+b)c+ab=a(b+c)+bc
f[i][j]f[i][j]f[i][j]为前iii个位置切了jjj块的答案。
f[i][j]=max⁡k=0i−1f[k][j−1]+sum[k]∗(sum[i]−sum[k])f[i][j]=\max_{k=0}^{i-1} f[k][j-1]+sum[k]*(sum[i]-sum[k])f[i][j]=maxk=0i1f[k][j1]+sum[k](sum[i]sum[k])

sum[k]2−f[k][j−1]=sum[i]∗sum[k]−f[i][j]sum[k]^2-f[k][j-1]=sum[i]*sum[k]-f[i][j]sum[k]2f[k][j1]=sum[i]sum[k]f[i][j]
那么每个状态表示成(sum[k],sum[k]2−f[k][j−1])(sum[k],sum[k]^2-f[k][j-1])(sum[k],sum[k]2f[k][j1]),然后维护凸壳即可。
因为斜率单调递增,可以斜率优化。
注意sum[i]sum[i]sum[i]可能等于sum[j]sum[j]sum[j],此时斜率可以记为inf。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long
#define LB long double

const int maxn=1e5+7;
const LL inf=1e13;

using namespace std;

int n,m,h,t,g,cnt;
int sum[maxn],q[maxn],fa[maxn][203],ans[maxn];
LL f[maxn][2];

LB get(int a,int b)
{
    if (sum[a]==sum[b]) return inf;
    LB deltay=(LB)sum[a]*(LB)sum[a]-(LB)sum[b]*(LB)sum[b]-f[a][g^1]+f[b][g^1];
    LB deltax=(LB)sum[a]-(LB)sum[b];
    return deltay/deltax;
}

int main()
{
    scanf("%d%d",&n,&m);
    m++;
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&sum[i]);
        sum[i]+=sum[i-1];
    }
    for (int i=0;i<=n;i++) f[i][0]=-inf;
    f[0][0]=0;
    g=0;
    for (int j=1;j<=m;j++)
    {
        h=t=1;
        q[t]=0;
        g^=1;
        for (int i=0;i<=n;i++) f[i][g]=-inf;
        for (int i=1;i<=n;i++)
        {
            while ((h<t) && (get(q[h],q[h+1])<=sum[i])) h++;
            f[i][g]=f[q[h]][g^1]+((LL)sum[i]-(LL)sum[q[h]])*(LL)sum[q[h]];
            fa[i][j]=q[h];
            while((h<t) && (get(q[t-1],q[t])>=get(q[t],i))) t--;
            q[++t]=i;
        }
    }
    printf("%lld\n",f[n][g]);
    int x=n,y=m;
    while (x>0)
    {
        x=fa[x][y];
        y--;
        ans[++cnt]=x;
    }
    for (int i=1;i<=cnt-1;i++) printf("%d ",ans[i]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值