【问题描述】
过去的日子里,农夫John的牛没有任何题目。可是现在他们有题目,有很多的题目。 精确地说,他们有 P 道题目要做。并且他们还离开了农场并且象普通人一样找到了工作,他们的月薪是M 元(上个月的工资在下月初发放)。
他们的题目是一流的难题,所以他们得找帮手。帮手们不是免费的,但是他们能保证在一个月内作出任何题目。每做一道题需要两笔付款:第一笔A_i(1 <= A_i <= M) 元在做题的那一个月初支付,第二笔B_i元(1 <= B_i <= M)在做完后的下一个月初支付。 每一个月牛们用上一个月挣的钱来付款。牛没有任何存款意识,所以每个月的节余都会拿去买糖吃掉。
因为题目是相互关连的,它们必须按顺序解出。 比如,题目3必须在解题目4之前或同一个月解出。
找出牛们做完所有题目并支付完所有款项的最短月数。
【输入格式】
第一行: M 和 P;
第2…P+1行: 第i行包含A_i和B_i, 分别是做第i道题的欲先付款和完成付款.
【输出格式】
第一行: 牛们做完题目并付完帐目的最少月数.
【输入样例】
100 5
40 20
60 20
30 50
30 50
40 40
【输出样例】
6
【样例解释】
【数据范围】
1 <= P <= 300
1 <= M <= 1000
第一种方法:(详见Cpp环境【Usaco2007 Jan】【CQYZOJ1432】解题)
状态函数f[i][j]=解答前i个problem且最后一次连续进行j个problem的解答所需要的最小月数。
第二种方法:(此法时间及空间复杂度更低)
代码参考USACO官方题解,注释为个人理解。(如有错误,敬请指出。)
#include<cstdio>
#define inf 0X7FFFFFFF
using namespace std;
int m,p,m1[305],m2[305];
int dp[305],cp[305];
//dp[i]:前i道题的最少月数
//cp[j]:前j道题的月数最少时,最后一个月支付的钱
int main()
{
scanf("%d%d",&m,&p);
for(int i=1;i<=p;i++)
{
scanf("%d%d",&m1[i],&m2[i]);
}
dp[0]=1;
for(int i=1;i<=p;i++)
{
dp[i]=inf;
int x=m1[i],y=m2[i];
for(int j=i-1;j>=0 && x<=m && y<=m;j--)
{
int z=dp[j]+2-(cp[j]<=m-x); //若付完第i题剩余的钱(m-x)可以支付记录的做完前j题的最后一个月月末付款的最小值(cp[j]),则只需要1个月,否则2个月。
if(z<dp[i] || (z==dp[i] && cp[i]>y)) //有大于一种情况时,记录最后一个月月末付款的最小值
{
dp[i]=z,cp[i]=y;
}
x+=m1[j];
y+=m2[j];
}
} //个人理解为j是一个分界点;j前面的在上个月或之前完成,j到i的题在本月完成。若有多个dp[j]最小,则找本月需付的上个月的钱的最小值。
printf("%d\n",dp[p]+1);
return 0;
}