洛谷P2389 电脑班的裁员(区间DP)

题目背景

隔壁的新初一电脑班刚考过一场试,又到了BlingBling的裁员时间,老师把这项工作交给了ZZY来进行。而ZZY最近忙着刷题,就把这重要的任务交(tui)给了你。

题目描述

ZZY有独特的裁员技巧:每个同学都有一个考试得分$ai(-1000<=ai<=1000)$,在n个同学$(n<=500)$中选出不大于k段$(k<=n)$相邻的同学留下,裁掉未被选中的同学,使剩下同学的得分和最大。要特别注意的是,这次考试答错要扣分【不要问我为什么】,所以得分有可能为负。

输入输出格式

输入格式:

第一行为n,k,第二行为第1~n位同学的得分。

输出格式:

一个数s,为最大得分和。

---------------------------我是分割线-----------------------------

强力安利出题人写的题解,里面竟然有$O(N)$的做法!

我这个蒟蒻不才,只好写一下$O(N^3)$的DP维持一下生活。

首先考虑建模,$f[i][j]$表示前i个数分成最多j个区间的最大价值,那么我们就可以枚举第j个区间的起点k,

那么如果 $(k,i)>=0$ ,状态转移式子为: $f[i][j]=max(f[i][j],f[k-1][j-1]+sum(k,i))$

如果 $(k,i)<0$ 就不选这个区间,式子为:$f[i][j]=max(f[i][j],f[k-1][j])$

最后还有一个点,如果整个数列没有一个正数,答案就是0

贴代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,k,a[501],s[501],i,j,m;
int f[501][501]; 
int main(){
    scanf("%d%d",&n,&m);
    for (i=1; i<=n; i++){
        scanf("%d",&a[i]);
        s[i]=s[i-1]+a[i];
    }
    memset(f,-10,sizeof(f));
    for (i=1; i<=n; i++){
        for (j=1; j<=i; j++)
           for (k=1; k<=j; k++)
           f[i][1]=max(f[i][1],s[j]-s[k-1]);
    }
    for (i=1; i<=n; i++)
      for (j=2; j<=i; j++){
           for (k=j; k<=i; k++)
               if (s[i]-s[k-1]>=0)
                   f[i][j]=max(f[i][j],f[k-1][j-1]+s[i]-s[k-1]);
               else f[i][j]=max(f[i][j],f[k-1][j]);
           f[i][j]=max(f[i][j],f[i][j-1]);
        }
    printf("%d",max(f[n][m],0));
    return 0;
}
原文地址:https://www.cnblogs.com/taduro/p/9462062.html