AFO

3675: [Apio2014]序列分割

Description

小H最近迷上了一个分隔序列的游戏。在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列。为了得到k+1个子序列,小H需要重复k次以下的步骤:

1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的序列——也就是一开始得到的整个序列);

2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列。

每次进行上述步骤之后,小H将会得到一定的分数。这个分数为两个新序列中元素和的乘积。小H希望选择一种最佳的分割方式,使得k轮之后,小H的总得分最大。


随手列个柿子就会发现断点的选择顺序对于答案是没有影响的

\(f_{ij}\)表示前i个数字里加了j个断点的最大贡献

\[f_{i,j}=max(f_{i-1,k}+sum_k\times (sum_i-sum_k))(k< i) \]

\(f\)表示\(f_{i-1}\)(。。。

那么对于\(k\)\(l\)更优当且仅当

\[f_k+sum_k\times (sum_i-sum_k)>f_l+sum_l\times (sum_i-sum_l) \]

\[s_i>\frac{sum_l^2-sum_k^2+f_l-f_k}{s_l-s_k} \]

斜率优化!!


#include<iostream>
#include<cstdio>
#include<cstring>
#define M 110001
#define LL long long 
using namespace std;

int u,m,n,k,q[M],t,h,pre[M][210];
LL f[2][M],s[M];
double xl(int k,int l,int u)
{
    if(s[k]==s[l]) return -1e18;
    return (1.0*(long double)(-f[u][l]+f[u][k]-((long double)s[k]*s[k])+((long double)s[l]*s[l]))*1.0/((long double)1.0*(-s[k]+s[l])));
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&m),s[i]=(LL)s[i-1]+m;
    for(int j=1;j<=k;j++)
    {
        h=t=0; u=j&1;
        for(int i=1;i<=n;i++)
        {
            while(t>h && xl(q[h+1],q[h],!u)<=(long double)s[i]) h++;
            int x=q[h]; pre[i][j]=x;
            f[u][i]=f[!u][x]+s[x]*(s[i]-s[x]);
            while(t>h && xl(q[t],q[t-1],!u)>=xl(i,q[t],!u)) t--;
            q[++t]=i;
        }
    }
    printf("%lld\n",f[k&1][n]);
    LL x=n;
    for(LL i=k;i>=1;i--)
    {
        printf("%lld ",pre[x][i]);
        x=pre[x][i];
    }
}
posted @ 2019-01-16 21:51  ZUTTER☮  阅读(145)  评论(0编辑  收藏  举报