您的位置:首页 > 其它

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

2017-01-12 15:11 302 查看
题目大意:将一个长度为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 205
using namespace std;
typedef long long LL;
LL sum
,f[2]
;
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]
);
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 2147483647
using 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
;
int l,r;
LL sum
,f[2]
;
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]
);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: