洛谷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; }