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


    题目描述

    你正在玩一个关于长度为 nn 的非负整数序列的游戏。这个游戏中你需要把序列分成 k + 1k+1 个非空的块。为了得到 k + 1k+1 块,你需要重复下面的操作 kk 次:

    选择一个有超过一个元素的块(初始时你只有一块,即整个序列)

    选择两个相邻元素把这个块从中间分开,得到两个非空的块。

    每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。

    输入输出格式

    输入格式:

    第一行包含两个整数 nn 和 kk。保证 k + 1 leq nk+1n。

    第二行包含 nn 个非负整数 a_1, a_2, cdots, a_na1,a2,,an (0 leq a_i leq 10^4)(0ai104),表示前文所述的序列。

    输出格式:

    第一行输出你能获得的最大总得分。

    第二行输出 kk 个介于 11 到 n - 1n1 之间的整数,表示为了使得总得分最大,你每次操作中分开两个块的位置。第 ii个整数 s_isi 表示第 ii 次操作将在 s_isi 和 s_{i + 1}si+1 之间把块分开。

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

    输入输出样例

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

    说明

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

    初始时你有一块 (4, 1, 3, 4, 0, 2, 3)(4,1,3,4,0,2,3)。在第 11 个元素后面分开,获得 4 imes (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)。在第 33 个元素后面分开,获得 (1 + 3) imes (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)。在第 55 个元素后面分开,获得 (4 + 0) imes (2 + 3) = 20(4+0)×(2+3)=20 分。

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

    限制与约定

    第一个子任务共 11 分,满足 1 leq k < n leq 101k<n10。

    第二个子任务共 11 分,满足 1 leq k < n leq 501k<n50。

    第三个子任务共 11 分,满足 1 leq k < n leq 2001k<n200。

    第四个子任务共 17 分,满足 2 leq n leq 1000, 1 leq k leq min{n - 1, 200}2n1000,1kmin{n1,200}。

    第五个子任务共 21 分,满足 2 leq n leq 10000, 1 leq k leq min{n - 1, 200}2n10000,1kmin{n1,200}。

    第六个子任务共 29 分,满足 2 leq n leq 100000, 1 leq k leq min{n - 1, 200}2n100000,1kmin{n1,200}。

    题解

    • 题目大意:有一个长度为n的序列,将其分成k+1块,每次分块将两个相邻的数从中间分开,每次贡献就是新分出来的两个块的元素和的乘积,最大化贡献
    • 显然,我们可以得到一个转移方程,f[i][j]=max(f[i−1][k]+sum[k]×(sum[j]−sum[k]))
    • 但是直接做会炸,然后斜率优化就好了

    代码

     1 #include <cstdio>
     2 #include <iostream>
     3 using namespace std;
     4 #define ll long long 
     5 #define sqr(x) (x)*(x)
     6 #define N 100010
     7 #define eps 1e18;
     8 int n,k,head,tail,Q[N],ans[210][N],a[N];
     9 ll sum[N],f[2][N];
    10 double calc(int i,int j,int k)
    11 {
    12     if (sum[i]==sum[j]) return -eps;
    13     return (f[(k+1)&1][j]-sqr(sum[j])-f[(k+1)&1][i]+sqr(sum[i]))*1.0/(sum[i]-sum[j]);
    14 }
    15 int main()
    16 {
    17     scanf("%d%d",&n,&k);
    18     for (int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    19     for (int i=1;i<=k;i++,head=tail=0)
    20         for (int j=1;j<=n;j++)
    21         {
    22             while (head<tail&&calc(Q[head],Q[head+1],i)<=sum[j]) head++;
    23             f[i&1][j]=f[(i+1)&1][Q[head]]+sum[Q[head]]*(sum[j]-sum[Q[head]]),ans[i][j]=Q[head];
    24             while (head<tail&&calc(Q[tail-1],Q[tail],i)>=calc(Q[tail],j,i)) tail--;
    25             Q[++tail]=j;
    26         }
    27     printf("%lld
    ",f[k&1][n]);
    28     for (int i=k,j=n;i>=1;i--) printf("%d ",j=ans[i][j]);
    29 }
  • 相关阅读:
    条件判断和循环
    list 和tuple的使用
    python的五大数据类型
    简单的一个程序,猜字游戏
    redhat7 nfs的配置以及auto自动挂载
    nmcli添加网卡 并且修改设备名字 添加IP地址
    RHEL7 系统ISCSI存储环境搭建
    Java分布式锁
    24个Jvm面试题总结及答案
    最新天猫3轮面试题目:虚拟机+并发锁+Sql防注入+Zookeeper
  • 原文地址:https://www.cnblogs.com/Comfortable/p/10402337.html
Copyright © 2020-2023  润新知