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

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

输入输出格式

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

第二行包含 n n n 个非负整数 a 1 , a 2 , ⋯   , a n a_1, a_2, \cdots, a_n a1,a2,,an ( 0 ≤ a i ≤ 1 0 4 ) (0≤a_i≤10^4) (0ai104),表示前文所述的序列。

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

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

输入输出样例

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

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

初始时你有一块 ( 4 , 1 , 3 , 4 , 0 , 2 , 3 ) (4, 1, 3, 4, 0, 2, 3) (4,1,3,4,0,2,3)。在第 1 1 1 个元素后面分开,获得 4 × ( 1 + 3 + 4 + 0 + 2 + 3 ) = 52 4 \times (1 + 3 + 4 + 0 + 2 + 3) = 52 4×(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)。在第 3 3 3 个元素后面分开,获得 ( 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)。在第 5 5 5 个元素后面分开,获得 ( 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 = 108 52 + 36 + 20 = 108 52+36+20=108 分。

限制与约定

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

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

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

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

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

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

分析:
对于相同位置的切法,不同的顺序切不影响答案。
显然 ( a + b ) c + a b = a ( b + c ) + b c (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]为前 i i i个位置切了 j j j块的答案。
f [ i ] [ j ] = max ⁡ k = 0 i − 1 f [ k ] [ j − 1 ] + s u m [ k ] ∗ ( s u m [ i ] − s u m [ 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])

s u m [ k ] 2 − f [ k ] [ j − 1 ] = s u m [ i ] ∗ s u m [ 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]
那么每个状态表示成 ( s u m [ k ] , s u m [ k ] 2 − f [ k ] [ j − 1 ] ) (sum[k],sum[k]^2-f[k][j-1]) (sum[k],sum[k]2f[k][j1]),然后维护凸壳即可。
因为斜率单调递增,可以斜率优化。
注意 s u m [ i ] sum[i] sum[i]可能等于 s u m [ 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]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值