题目描述
贪玩的企鹅最近一个月因为沉迷游戏都没有好好学习。欠下了n道题的他突然得知老师要突击检查,企鹅一把鼻涕一把泪地说服了老师稍微放宽一些检查条件,最终老师决定随机选取连续的m道题来检查,在这m道题之中只要企鹅完成了两道题就不再追究企鹅这一个月拖欠作业的问题。
由于每道题的难易程度不同,完成每道题所需要的时间不同。现在,企鹅想知道如果才能用最少的时间完成某些题以确保自己能通过检查,你能帮帮他吗?
其实就是在一个n个元素的序列中选择一些,使任意长度为m的区间内都有2个已选元素。选择每一个元素有不同代价,求代价的最小值。
分析
第一时间一定会想到贪心直接扫一遍,但是肯定会WA掉的。因为在长度为m的区间里面不一定只能选最小的两个,选靠后的还可以让后面的一些区间所选的元素减少甚至根本不需要选。所以说贪心是肯定有后效性的!!
然后就很容易想到动态规划,但是。。。。。。本蒟蒻一开始也就只能想到“动态规划”这个算法了23333
一开始想到的是f(i)表示前i道题选择一些,此时满足任意长度为m的序列(对于1~i)中都有2道选了的题所需要的最小时间,但是这个显然是推不动的,因为不知道前面元素具体选择的情况,一些限定条件无法得知。
一维不行那就二维吧~f(i,j),i表示前i道题这个是没太大问题的,那么关键就是j如何设了。
首先如果设1~i中的题选了第j道,这个是肯定做不出来的。
那再想想吧
。。。
。。。
。。。
好像不是很好想,感觉就是少了些什么,总是感觉要三维,但是三维又会超时。
既然分析j分析不出来什么,那就只能都回去看看在i上能不能改一改。在前面所说的每一个状态的问题都是不能知道具体选了哪两道题,所以说我们可不可以这样想:我们设f(i,j)表示前i道题中,i必须选择,此时选了的题中距i最近的一道题距离i的长度为j(即上一个选择了的题为i-j)(其实可以直接设上一个选了的题为j,设长度只是为了方便),此时1~i满足条件所需要的最短时间。
这样似乎将原本需要三维表示的状态降成了二维(降维打击!!!),现在看看能不能递推得动。
首先f(i,j)中,i和(j-i)这两道题是肯定选了的,所以说f(i,j)的值只需要在f(i-j,x)中选一个最小的,然后再加上做第i道题的时间即可,那么现在的问题就是分析x的范围了。
因为当选了i和i-j之后,对于i-1这一道题(i-1是肯定没做的),若要满足题目条件则需要第(i-m)~(i-j-1)道题中必须做一道(因为已经做了i-j了),此时i-j距离i-m的距离x最大为m-j,所以x上界为m-j。
所以说就可以得到状态转移方程了:f(i,j)=min{f(i-j,x) | 1<=x<=m-j}+a[i]
边界条件f(0,j)=0,其他的全部等于inf
最终答案ans=min{f(i,j) | i>=n-m+2 && j<=m-1 && i-j>=n-m+1}
(其中:i>=n-m+2以及i-j>=n-m+1是为了保证最后m个有做2道题,j<=m-1是为了满足题目条件)
这里还有一个问题,就是实现代码时i需要循环一次,j需要循环一次,x也需要循环一次,相当于还是三重循环,会超时。但是看到x的范围:1~m-j,左端点固定,右端点滑动,很容易想到用滑动窗口优化。
其实感觉“降维打击”挺常用的啊。。为什么我想了这么久orz orz orz orz orz
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<set>
#include<map>
using namespace std;
const int maxn=5005;
const long long inf=5e12+5;
int n,m,a[maxn];
long long d[maxn][maxn],minv[maxn][maxn];
void _scanf(int &x){
x=0;
bool flag=0;
char op=getchar();
while(!isdigit(op)){
if(op=='-'){
flag=1;
}
op=getchar();
}
while(isdigit(op)){
x=x*10+op-'0';
op=getchar();
}
if(flag){
x=-x;
}
return;
}
void workin(){
_scanf(n);
_scanf(m);
for(int i=1;i<=n;i++){
_scanf(a[i]);
}
return;
}
void dp(){
memset(d,0,sizeof(d));
memset(minv,0,sizeof(minv));
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
d[i][j]=inf;
minv[i][j]=inf;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m-1;j++){
if(i-j>=0){
d[i][j]=minv[i-j][m-j]+a[i];
}
minv[i][j]=min(d[i][j],minv[i][j-1]);
}
}
return;
}
void solve(){
dp();
long long ans=inf;
for(int i=n-m+2;i<=n;i++){
for(int j=1;j<=m-1 && i-j>=n-m+1;j++){
ans=min(ans,d[i][j]);
}
}
cout<<ans;
return;
}
int main(){
freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
workin();
solve();
return 0;
}