[BZOJ 3675][APIO 2014]序列分割(斜率优化DP)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=3675

思路

这题不是很难,但是坑了我一个下午+半个晚上才做出来,郁闷
首先设 f[i][k]=ik 得到的分数, sum[i]=it=1At ,即序列 A 的前缀和
很容易推出DP方程:
f[i][k]=max{f[j][k1]+sum[j](sum[i]sum[j])}
f[i][k]=max{f[j][k1]+sum[i]sum[j]sum[j]2)}
这个DP复杂度是 O(n2k) 的,然后因为APIO很恶心地卡内存(128MB),需要拿滚存减少内存占用,粗略估计如果常数优化得不错的话,在实际比赛中能拿到50分左右(貌似apio的cu就到手了2333)。
然后看看能不能优化一下,发现这个DP其实很像一个斜率优化DP,可以通过优化来降低一维复杂度,变成大约是 O(nk) 的DP,恩可以拿到满分
假设在DP到 f[i][t+1] 时,有两个状态 f[j][t] f[k][t] 可供参考,假设 k<j ,且 jk
f[j][t]+sum[i]sum[j]sum[j]2>f[k][t]+sum[i]sum[k]sum[k]2
f[j][t]sum[j]2f[k][t]+sum[k]2>sum[i]sum[k]sum[i]sum[j]
sum[j]2f[j][t]sum[k]2+f[k][t]>sum[i](sum[k]sum[j])
sum[j]2f[j][t]sum[k]2+f[k][t]sum[j]sum[k]>sum[i]
再设 y[i][t]=sum[i]2f[i][t]
y[j][t]y[k][t]sum[j]sum[k]>sum[i]
这玩意长得挺像斜率的。。。上面的不等式左边当然是越大越好,因此最终我们需要维护一个上凸壳,每次决策时首先将那些队首肯定不能充当最优解,而且以后也不能的元素都弹出队列,然后用当前的队首去更新 f ,然后将这个新的 f 放入队尾,入队前要维护这个队列的单调性(上凸壳)
咦。。。好像原题中是要求输出一个最大方案的(看来BZOJ的管理员太懒不愿意造第二问数据2333),这个就记录下DP的前驱就好了
后记:
1、最好不要直接写斜率的表达式,因为如果分母是0那么就会出问题,最好把分母移到不等式的另一边去
2、每次决策的2次出队过程必须时刻保证队列中至少有2个元素。

代码

第一次做的,没带读入优化,整整10s

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 100010
#define EPS 1e-6
#define INF 1000000000

using namespace std;

typedef long long int LL;

int n,m,h,t,q[MAXN];
LL f[MAXN][2],sum[MAXN],y[MAXN][2]; //f[i][j]=长度为i的序列,一共划分了j次得到的最大分数
int now=0; //now=当前用的下标

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&sum[i]);
        sum[i]+=sum[i-1];
    }
    for(int i=1;i<=n;i++)
        y[i][0]=sum[i]*sum[i];
    for(int j=1;j<=m;j++) //划分j次
    {
        now=1-now;
        h=1,t=1;
        q[h]=0;
        for(int i=1;i<=n;i++) //长度为i的序列划分j次
        {
            while(h<t&&sum[i]*(sum[q[h+1]]-sum[q[h]])>=(y[q[h+1]][1-now]-y[q[h]][1-now])) h++;
            f[i][now]=f[q[h]][1-now]+sum[i]*sum[q[h]]-sum[q[h]]*sum[q[h]];
            y[i][now]=sum[i]*sum[i]-f[i][now];
            while(h<t&&(y[q[t]][1-now]-y[q[t-1]][1-now])*(sum[i]-sum[q[t]])>=(y[i][1-now]-y[q[t]][1-now])*(sum[q[t]]-sum[q[t-1]])) t--;
            q[++t]=i;
        }
    }
    printf("%lld\n",f[n][now]);
    return 0;
}

第二次加入了读入优化,略微快了几百毫秒

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 100010
#define EPS 1e-6
#define INF 1000000000

using namespace std;

typedef long long int LL;

int n,m,h,t,q[MAXN];
LL f[MAXN][2],sum[MAXN],y[MAXN][2]; //f[i][j]=长度为i的序列,一共划分了j次得到的最大分数,y[i][t]=sum[i]*sum[i]-f[i][t]
int now=0; //now=当前用的下标

inline LL read()
{
    LL ans=0;
    char ch=getchar();
    while(ch==' '||ch=='\n') ch=getchar();
    while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
    return ans;
}

int main()
{
    n=(int)read(),m=(int)read();
    for(int i=1;i<=n;i++)
    {
        sum[i]=read();
        sum[i]+=sum[i-1];
    }
    for(int i=1;i<=n;i++)
        y[i][0]=sum[i]*sum[i];
    for(int j=1;j<=m;j++) //划分j次
    {
        now=1-now;
        h=1,t=1;
        q[h]=0;
        for(int i=1;i<=n;i++) //长度为i的序列划分j次
        {
            while(h<t&&sum[i]*(sum[q[h+1]]-sum[q[h]])>=(y[q[h+1]][1-now]-y[q[h]][1-now])) h++;
            f[i][now]=f[q[h]][1-now]+sum[i]*sum[q[h]]-sum[q[h]]*sum[q[h]];
            y[i][now]=sum[i]*sum[i]-f[i][now];
            while(h<t&&(y[q[t]][1-now]-y[q[t-1]][1-now])*(sum[i]-sum[q[t]])>=(y[i][1-now]-y[q[t]][1-now])*(sum[q[t]]-sum[q[t-1]])) t--;
            q[++t]=i;
        }
    }
    printf("%lld\n",f[n][now]);
    return 0;
}

你可能感兴趣的:([BZOJ 3675][APIO 2014]序列分割(斜率优化DP))