BZOJ 3675 [Apio2014] 序列分割 斜率优化

来源:互联网 发布:中老年人学英语软件 编辑:程序博客网 时间:2024/05/01 07:40

题目大意:将一个长度为n的非负整数序列分割成k+1个非空的子序列,即将序列切割k次。首先选择一个长度超过1的序列;然后选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列。每次进行上述步骤之后,将会得到一定的分数。这个分数为两个新序列中元素和的乘积。求最大得分。

首先,对于一段序列想分成A|B|C|D四份,先分成A|BCD与先AB|CD的得分是相同的。这样这个问题就具有了子问题的性质,可以DP。设状态f(i,j)代表在i处分割、已经分割了k次的最大得分,状态转移方程为这里写图片描述

//O(kn2)TLE#include <cstdio>#include <algorithm>#include <cstring>#define N 100005#define M 205using namespace std;typedef long long LL;LL sum[N],f[2][N];int main() {    int n,m,x;    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++) scanf("%d",&x) , sum[i]=sum[i-1]+x;    int o=0;    for(int i=1;i<=m;i++) {        memset(f[o],0,sizeof f[o]);        for(int j=1;j<=n;j++)            for(int k=1;k<j;k++)                f[o][j]=max(f[o][j],(sum[j]-sum[k])*sum[k]+f[o^1][k]);        o^=1;    }    printf("%lld\n",f[o^1][n]);    return 0;}

考虑怎么优化。
设P为f(i,k)的一个可能的值。
将P代入,max去掉,将式子变形,得到这里写图片描述
设x=sum(j),y=-sum(j)2+f(j,k-1)(偷懒没用公式编辑器凑合看一下),将式子变为
这里写图片描述很明显的一个y=kx+b的形式。
k=sum(i)为定值,想让P最大,也就是让(-P)最小,即最小化截距.
可以发现所选的点一定在下凸包上(自行画个图理解一下吧我不画了…)
并且发现查询的斜率单调,维护一个单调队列,时间复杂度O(kn)

#include <cstdio>#include <algorithm>#include <cstring>#define N 100005#define M 205#define INF 2147483647using namespace std;typedef long long LL;struct Point {    LL x,y;    double slope;    Point(LL a,LL b):x(a),y(b),slope(0.0){}    Point(){}    //get_slope    double operator ^ (const Point& rhs) const {        if(x==rhs.x) return (rhs.y > y ? INF : -INF);        return (double)(y-rhs.y)/(x-rhs.x);    }}q[N];int l,r;LL sum[N],f[2][N];void Insert(Point o) {    double s=0;    while(l<r) {        s=q[r-1]^o;        if(q[r-1].slope>s) r--;        else break;    }    q[r++]=o; q[r-1].slope=s;    return ;}LL get_ans(double k) {    while(l!=r-1) {        if(q[l+1].slope<k) l++;        else break;    }    return k*q[l].x-q[l].y;}int main() {    int n,m,x;    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++) scanf("%d",&x) , sum[i]=sum[i-1]+x;    int o=0;    for(int i=1;i<=m;i++) {        l=r=1;        for(int j=2;j<=n;j++) {            Insert(Point(sum[j-1],sum[j-1]*sum[j-1]-f[o^1][j-1]));            f[o][j]=get_ans(sum[j]);        }        o^=1;    }    printf("%lld\n",f[o^1][n]);    return 0;}
0 0
原创粉丝点击