斜率优化 _洛谷_P3648 [APIO2014]序列分割

 题目链接:洛谷_P3648 [APIO2014]序列分割

题目描述

你正在玩一个关于长度为 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 之间把块分开。

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

输入输出样例

     输入                                              输出 
      7 3                                108
      4 1 3 4 0 2 3                      1 3 5
 

解题思路

首先要证明切割顺序与答案无关

      设一组元素分别为a,b,c

      两种切割方式  第一种:     (a,b,c) =>(a,b),(c) => (a),(b),(c)   (a+b)*c+a*b  =a*c+b*c+a*b   

                             第二种:     (a,b,c) =>(a),(b,c) => (a),(b),(c)   (b+c)*a+b*c  =a*c+a*c+b*c

      因此不用考虑写个顺序

根据题意很容易得出 要用动态规划
让我们先尝试写一下动态转移方程:
sumi1~i的元素前缀和,
fi,j表示为前i个元素,一共切割了j次,很容易得出如下方程:

                  fi,j=max{fk,j-1+sumk∗(sumi−sumk)}(0≤k<i)

这么写肯定会TLE,这时我们就想想该如何它的优化时间复杂的。

 

那用什么呢?

答案就是 :

斜率优化

 

首先,数组的后一维 j 每次都是通过 j-1得出,显然我们可以使用滚动数组

得方程如下:

                      fi=max{fk+sumk∗(sumi−sumk)}(0≤k<i)

假设k1>k2,考虑选取fi_k1比fi_k2更优,得出如下两个式子

                      fi_k1=fk1+sumk1∗(sumi−sumk1)=fk1+sumk1∗sumi−sumk1*sumk1

                      fi_k2=fk2+sumk2∗(sumi−sumk2)=fk2+sumk2∗sumi−sumk2*sumk2

    由于fi_k1>fi_k2 ,合并则

                         fk1+sumk1∗sumi−sumk1*sumk1>fk2+sumk2∗sumi−sumk2*sumk2

                           fk1-fk2+sumk2*sumk2−sumk1*sumk1>(sumk2-sumk1)∗sumi       (由于sum单调递增,所以sumk2-sumk1>0)

                           (fk1-fk2+sumk2*sumk2−sumk1*sumk1)/(sumk2-sumk1)>sumi

 


 

   然后T(k1,k2)= ( fk1-fk2+sumk2*sumk2−sumk1*sumk1)/(sumk2-sumk1)

   若T(k1,k2)>sumi 则fi_k1比fi_k2更优  (q是记录备选方案的队列)

     于是:

             踢出队列    T(q[head],q[head+1]) ≤ sumi     head++;  (选择队头后一位的情况比选择队头优,就把队头踢出队列)

           更新队尾    T(q[tail-1],q[tail])≥T(q[tail],q[i])    tail--;       (证明如下)

      如果 T(q[tail-1],q[tail])≥T(q[tail],q[i]) 时,q[tail]仍在队列中,则q[tail]在某一刻是最优方案

      说明q[tail]比q[tail-1]  q[i]不如q[tail]优,根据 上文证明“若T(k1,k2)>sumi 则fi_k1比fi_k2更优”

      T(q[tail-1],q[tail])>sumi   T(q[tail],q[i])≤sumi  

       合并得  T(q[tail],q[i])≤sumi<T(q[tail-1],q[tail])

      因此 T(q[tail-1],q[tail])≥T(q[tail],q[i]) 不成立,要将q[tail]踢出队列

 

 代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
int n,k;
int a[100005],q[100005],head,tail,way[100002][212];
long long f[100002],g[100002],sum[100002];
double t(int k1,int k2)
{
    if (sum[k2]==sum[k1]) return-1;
    return (((double)(g[k1]-g[k2]-sum[k1]*sum[k1]+sum[k2]*sum[k2])/(double)(sum[k2]-sum[k1])));
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
     {
         scanf("%d",&a[i]);
         sum[i]=sum[i-1]+a[i];
     }
    for(int l=1;l<=k;l++)
    {
    head=tail=1; q[1]=0;    
      for(int i=2;i<=n;i++)
        g[i]=f[i];
      for(int i=1;i<=n;i++)
         {
        while(head<tail && t(q[head],q[head+1])<=(double)sum[i]) head++;        
        f[i]=g[q[head]]+sum[q[head]]*(sum[i]-sum[q[head]]);
        way[i][l]=q[head];
        while(tail>head && t(q[tail-1],q[tail])>=t(q[tail],i)) tail--;
        tail++;
        q[tail]=i;
      }
    }
    printf("%lld\n",f[n]);
    int i=n,j=k;
    while(way[i][k])
    {
     printf("%d ",way[i][k]);
     i=way[i][k];k--;
   }
}

 

posted @ 2020-08-20 18:47  Li_yxxx  阅读(90)  评论(0)    收藏  举报