题解 P1687 【机器人小Q】
LeavingZzz · · 题解
\mathsf{Solution\space For\space P1687}
Update On 2022/11/17 : 修了一处 typo 与若干个不合理的公式。
非常好的一道分类讨论类的 dp。
\mathsf{Analysis}
首先我们设计状态
令
但是很快你就会发现这样设计状态是有bug的,因为每天充电的上限是
令
(即,以天数为第一关键字,以最后一天充电的时长为第二关键字选取最优解)
然后研究状态转移方程
对于第
如果我们选取第 i 个能量单位
此时当前状态
f[i-1][j-1][0]+w[i]>119
这时候我们应该增加新的一天来使用第
因此比较
若
那么此时选取第
若
f[i-1][j-1][0]+w[i]\le119
这时候考虑将第
若
此时选取第
若
此时比较第二关键字
如果我们不选取第 i 个能量单位
这时候直接继承前面的状态
分析完毕,还有问题请看代码QwQ
\mathsf{Code}
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=3007;
int F[maxn][maxn][2];
int N,K;
int w[maxn],cnt;
int main()
{
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
#endif
scanf("%d%d",&N,&K);
for(int i=1;i<=N;i++)
{
scanf("%d",&w[++cnt]);
if(w[cnt]>119) --cnt;//大于119可以直接丢掉,怎么都用不上
}
//如果可用的能量单位不足K个那么无解
if(cnt<K) {printf("You can't do it.");return 0;}
memset(F,0x3f,sizeof(F));//初始化
for(int i=0;i<=cnt;i++)//无论是多少个数字分成0段花费0天
F[i][0][1]=0;
for(int i=1;i<=cnt;i++)
for(int j=1;j<=min(i,K);j++)
{
//先继承
F[i][j][0]=F[i-1][j][0];
F[i][j][1]=F[i-1][j][1];
//考虑选取第i个
//是否需要新增一天
if(F[i-1][j-1][0]+w[i]>119)
{
if(F[i-1][j-1][1]+1<F[i][j][1])
F[i][j][1]=F[i-1][j-1][1]+1,F[i][j][0]=w[i];
else if(F[i-1][j-1][1]+1==F[i][j][1])
F[i][j][0]=min(w[i],F[i][j][0]);
}
//直接追加到最后一天
else
{
if(F[i-1][j-1][1]<F[i][j][1])
F[i][j][1]=F[i-1][j-1][1],F[i][j][0]=F[i-1][j-1][0]+w[i];
else if(F[i-1][j-1][1]==F[i][j][1])
F[i][j][0]=min(F[i-1][j-1][0]+w[i],F[i][j][0]);
}
}
/*for(int i=1;i<=cnt;i++)
for(int j=1;j<=min(i,K);j++)
printf("F[%d][%d]={%d,%d}\n",i,j,F[i][j][0],F[i][j][1]);*/
printf("%d",F[cnt][K][1]);//答案即为F[cnt][K][1]
return 0;
}
谢谢管理大大审核^_^