BZOJ3675 [Apio2014]序列分割
题
link:http://www.lydsy.com/JudgeOnline/problem.php?id=3675
简介
网上大佬的题解都那么复(qi)杂(guai)。。。
然而我做着感觉很轻松哎。。难道是我做错了?
题解
fi,k表示前i个数被划分了k次的最大获利
那么
fi,k=max{(si−sj)×sj+fj,k−1}
为了好看把k这一维去掉,令 gj=fj,k−1 , fi表示原来的fi,k
那么
fi=max{gj+(si−sj)×sj}
把max去掉得到
fi=(gj−s2j)+si×sj
那么 fi 就是截距,由于 sj 是单调不下降的所以把它作为横坐标,那么 (gj−s2j) 就是纵坐标了
为了好看再写一步,
令 xj=sj , yj=(gj−s2j)
那么原方程化为
fi=si×xi+yi
斜率 (−si) 是单调不上升的,因此我们可以使用单调队列维护上凸壳,时间复杂度 O(NK)
有一个坑点:因为横坐标是单调不下降的,所以就有可能出现斜率不存在的直线,此时分母为0,应当特判
#include <cstdio>
#include <algorithm>
#define maxn 100010
#define maxk 210
#define ll long long
using namespace std;
struct point{ll x, y;double k;}q[maxn];
ll f[2][maxn], s[maxn], N, K;
ll read(ll x=0){scanf("%lld",&x);return x;}
void init()
{
ll i, a;
N=read();K=read();
for(i=1;i<=N;i++)a=read(),s[i]=a+s[i-1];
}
void work(ll *f, ll *g, ll c)
{
ll l=1, r=1, i, x, y, flag;
q[r++]=(point){s[c],g[c]-s[c]*s[c],0};
for(i=c+1;i<=N;i++)
{
while(l<r-1 and -s[i]<q[l+1].k)l++;
f[i]=q[l].y+s[i]*q[l].x;
x=s[i],y=g[i]-s[i]*s[i];
if(x==q[r-1].x and y>q[r-1].y)r--;
else if(x==q[r-1].x and y<=q[r-1].y)continue;
while(r-1>l and double(y-q[r-1].y)/(x-q[r-1].x) > q[r-1].k)r--;
q[r]=(point){x,y,double(y-q[r-1].y)/(x-q[r-1].x)},r++;
}
}
int main()
{
ll i;
init();
for(i=1;i<=K;i++)work(f[i&1],f[~i&1],i);
printf("%lld\n",f[K&1][N]);
return 0;
}