[APIO2014] 序列分割

Description

给定一个长度为 (n) 的序列,需要将其划分成 (k+1) 份,每次划分时会将一个段划分成两个段,本次操作的得分为划分成的两个段的和的乘积。求最大得分和为多少。

Solution

容易证明操作顺序对答案没有影响,具体可以证明个结合律,代数系统里面有过类似的东西。

(f(i,j)) 表示前 (j) 个数分了 (i) 次的最大得分,则显然有转移

[f(i,j) leftarrow f(i-1,k) + s_k (s_j-s_k) ]

转化一下,并记 (f_j = f(i,j), g_j = f(i-1,j)),则有

[s_k^2 - g_k = s_j s_k - f_j ]

于是,考虑设 (Y(k) = s_k^2 - g_k , K(j) = s_j, X(k) = s_k, B(j) = -f_j),斜率优化即可,维护一个斜率单调递增的单调队列。

自闭原因:数组大小写反,卡精度

#include <bits/stdc++.h>
using namespace std;

#define int long long 
#define double long double
const int N = 100005;
const int M = 205;

int n,k,a[N],s[N],q[N],f[N],g[N],head=0,tail=0;
int from[M][N];

double Y(int k)
{
    return s[k]*s[k]-g[k];
}
double X(int k)
{
    return s[k];
}
double K(int j)
{
    return s[j];
}
double B(double Y,double K,double X)
{
    return Y-K*X;
}



double slope(int p,int q)
{
    double dy=Y(p)-Y(q);
    double dx=X(p)-X(q);
    if(abs(dx)<1e-9)
    {
        return -1e32;
    }
    else 
    {
        return dy/dx;
    }
}

signed main()
{
    // freopen("p3648.in","r",stdin);

    ios::sync_with_stdio(false);

    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];

    for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];

    for(int i=1;i<=k;i++)
    {
        for(int j=1;j<=n;j++) g[j]=f[j];
        head=0;
        tail=-1;
        q[0]=0;
        for(int j=1;j<=n;j++)
        {
            while(head<tail && slope(q[head],q[head+1])<=K(j)) ++head;
            if(head<=tail)
            {
                int k=q[head];
                f[j]=g[k]+s[k]*(s[j]-s[k]);
                from[i][j]=k;
            }
            else f[j]=0;
            while(head<tail && slope(q[tail-1],q[tail])>=slope(q[tail],j)) --tail;
            q[++tail]=j;

        }
    }

    cout<<f[n]<<endl;

    int i=k, j=from[k][n];
    while(i)
    {
        cout<<j<<" ";
        j=from[--i][j];
    }
}

原文地址:https://www.cnblogs.com/mollnn/p/14040409.html