poj1042

11 篇文章 0 订阅

题目有难度。大致题意为小明要去钓鱼。现有n个池塘可以钓鱼。每个池塘开始有鱼fi个,每隔5分钟,就减少di个,剩余fi-di个鱼可以钓。小明可以从一个池塘移动到下一个池塘,从第i个池塘移动到第i+1个池塘的划分的时间为ti*5分钟。现有h小时,问如何钓鱼才能使得钓到的鱼最多,在相等的时候, 要求前面编号的池塘钓鱼个数尽可能多。

此题可用dp也可用贪心。我两种方法多尝试过。其中贪心测试用例都过,但WA,现在首先介绍一下dp。

这里令dp[i][j]表示第i个池塘最多可以钓j时间时,从i——n个池塘总共可以钓到鱼的最大数目。且令ti[i][j]表示此时第i个池塘最多可以钓鱼的时间。则问题可以转化为最大钓鱼数目为dp[1][h],第1个池塘钓鱼ti[1][h],第2个池塘钓鱼ti[2][h-ti[1][h]-t[1]],以此类推便可以计算出所有池塘的钓鱼个数。

状态转移方程分析如下:计算dp[i][j],可以从dp[i+1][]与dp[i][]的关系入手。上面已经说明了dp[i][j]的含义:第i个池塘最多可以钓j时间时,从i——n个池塘总共可以钓到鱼的最大数目。那么先假定第i个池塘的钓鱼个数确定为x,费时t。则dp[i][j]=x+dp[i+1][j-t-tt],其中tt为从第i个池塘移动到第i+1个池塘所需的时间。这个式子的含义也即既然第i个池塘已经花费了t时间,那么剩下的时间为j-t,也即留给第i+1个池塘的最多时间为j-t还要减去从第i个池塘移动到第i+1个池塘所需的时间。那么就有了上述式子的产生。这里,由于第i个池塘钓鱼时间是不确定的,故还需枚举第i个池塘所需钓鱼的时间,分别求出上述式子导出的dp值,然后比较求出最大值。即为dp[i][j]值。同时由于要求在不影响总的钓鱼个数的情况下前面池塘钓鱼时间尽可能多。故这里在枚举的时候还要注意一些细节,使得当钓鱼个数相同时,选择第i个池塘钓鱼时间多的一种方案作为ti[i][j]。

dp状态转移方程: dp[i][j]=max{dp[i+1][j-t[i]-k]+fish[i][k]}   ( 0<=k<=j)

                                  ti[i][j]=k(在dp相同情况下k尽可能大)

实现起来有两种方式:

一)建立备忘录

即采用递归的方式求解dp,从高层一直递归到底层。然后计算底层数据,作为已知条件返回到上一层。上一层采取同样的方式不断返回。最终求解顶层数据,递归结束。dp通常的方式为for循环先计算底层,在计算顶层。在形式上与递归不同,但是本质都是由底层推出顶层。

下面是备忘录代码: 204K+516MS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define nMax 30
#define hMax 200 // h转化成的最大5分钟个数
int f[nMax]; // 记录f
int d[nMax]; //记录d
int t[nMax]; //记录间隔时间t
int fish[nMax][hMax];//记录每个池塘分别钓鱼0——h时间,钓到的鱼数
int dp[nMax][hMax]; // dp[i][j]:第i个池塘最多钓j时间时,i——n池塘总共钓鱼的最大数目
int ti[nMax][hMax]; // time[i][j]:第i个池塘最多钓j时间时,i——n池塘总共钓鱼的最大数目时,第i个池塘所能钓到鱼的最多数目
int n,h; 
int dfs(int number,int tt){ //备忘录求解dp
	if(tt<0) // 若tt<0,则说明数据不合理,直接返回0,0<=tt<=h
		return 0;
	if(dp[number][tt]!=-1) // 若已经计算则直接返回计算值(备忘录的核心,也是备忘录区别暴力的关键)
		return dp[number][tt];
	if(number==n){ // 若到了最底层,即第n个池塘,没有下一个池塘了。则根据dp含义直接计算
		dp[number][tt]=fish[number][tt]; // dp直接等于fish
		ti[number][tt]=tt; // ti直接等于tt
		return dp[number][tt]; // 返回dp
	}
	int max=-1,index;
	for(int i=0;i<=tt;i++){ // 枚举第i个池塘可以钓鱼的时间(0<=i<=tt)
		int temp=dfs(number+1,tt-t[number]-i); // 状态转移方程:dp[i][j]=dp[i+1][j-t[i]-k]+fish[i][k]
		if(fish[number][i]+temp>=max){ // 比较取最大值,注意这里是>=号不能单纯为>号,相同dp下,时间取大值
			max=fish[number][i]+temp;
			index=i; 
		}
	}
	dp[number][tt]=max; // 最大钓鱼数目赋值dp
	ti[number][tt]=index; // 最大钓鱼数目同时第i个池塘最大钓鱼时间赋值
    return max; // 返回dp值
}
int main(){
	int i,j;
    while(scanf("%d",&n),n){
		scanf("%d",&h);
		h*=12;
		for(i=1;i<=n;i++) //输入f
			scanf("%d",&f[i]);
		for(i=1;i<=n;i++){ //输入d同时计算fish
			scanf("%d",&d[i]);
			int in=f[i];
			fish[i][0]=0; // 初始化为0
			for(j=1;j<=h;j++){
				fish[i][j]=in+fish[i][j-1]; //递推式
				in-=d[i]; 
				in=in>=0?in:0; //若小于0,则作为0计算
			}
		}
		for(i=1;i<n;i++) // 输入t
		    scanf("%d",&t[i]);		
		memset(dp,-1,sizeof(dp)); // 初始化为-1,便于备忘录实现
	    int result=dfs(1,h); // 最大钓鱼数目
		printf("%d",5*ti[1][h]); //输出第一个池塘钓鱼时间
		h-=(ti[1][h]+t[1]); // 第二个池塘最大钓鱼时间
		for(i=2;i<=n;i++){ // 依次输出第2、3、……、n个池塘钓鱼时间
			if(h>0){ // 注意h可能小于0,那么输出就要改变为0
			printf(", %d",5*ti[i][h]); // h>0时,则正常输出
			h-=(ti[i][h]+t[i]); //计算下一个池塘的最大钓鱼时间
			}
			else // h<=0,则均输出为0
				printf(", 0");
		}
		printf("\nNumber of fish expected: %d\n\n",result); // 输出最大钓鱼数目
	}
	return 0;
}
	

二)常规方法实现dp,区别于备忘录在于不使用递归(没有了系统递归堆栈的使用,时间更快,确定是必须所有的dp都要计算,可能有些dp不需要计算。而备忘录则很好的解决了这一点,备忘录所计算的都是必须要用到的dp,利用效率极高,几乎为100%,没有多余)。

下面是代码:204K+422MS

#include <stdio.h>
#include <stdlib.h>
#define nMax 30
#define hMax 200
int f[nMax];
int d[nMax];
int t[nMax];
int fish[nMax][hMax];
int dp[nMax][hMax];
int ti[nMax][hMax];
int n,h;
int main(){
	int i,j,k;
    while(scanf("%d",&n),n){
		scanf("%d",&h);
		h*=12;
		for(i=1;i<=n;i++)
			scanf("%d",&f[i]);
		for(i=1;i<=n;i++)
			scanf("%d",&d[i]);
		for(i=1;i<n;i++)
		    scanf("%d",&t[i]);
		for(i=1;i<=n;i++){ //计算fish,此方法没有上述方法效率高,但最后时间缺更短,足可以说明常规for计算dp在时间上优于备忘录
			for(j=0;j<=h;j++){
				fish[i][j]=0;
				int temp=f[i];
				for(k=1;k<=j;k++){
					if(temp==0)
						break;
					fish[i][j]+=temp;
					temp-=d[i];
					if(temp<0)
						temp=0;
				}
			}
		}
		for(i=0;i<=h;i++){ // 直接计算dp[n][0——h],ti[n][0——h]
			dp[n][i]=fish[n][i];
			ti[n][i]=i;
		}
		for(i=n-1;i>=1;i--){ // for循环计算dp[i][j]
			for(j=h;j>=0;j--){ 
			    int max_dp=-1,index; // 注意max_dp的初始值要小于0,因为dp[i][j]可能等于0
				for(k=0;k<=j;k++){ // 枚举第i个池塘可能的钓鱼时间
					int temp=fish[i][k];
					if(j-t[i]-k>=0) // 时间要在0——h之间,否则看作0
						temp+=dp[i+1][j-t[i]-k];//状态转移方程:dp[i][j]=dp[i+1][j-k-t[i]]+fish[i][k]
						index=k;
					if(temp>=max_dp){//计算最大值,注意<=号
						max_dp=temp;
					}
				}
				ti[i][j]=index;//赋值ti
				dp[i][j]=max_dp; //赋值dp
			}
		}
	    int result=dp[1][h]; //同上分析
		printf("%d",5*ti[1][h]);
		h-=(ti[1][h]+t[1]);
		for(i=2;i<=n;i++){
			if(h>=0){
			printf(", %d",5*ti[i][h]);
			h-=(ti[i][h]+t[i]);
			}
			else
				printf(", 0");
		}
		printf("\nNumber of fish expected: %d\n\n",result);
	}
	return 0;
}


接下来讲述用贪心实现

贪心准则:每次都选择五分钟内钓鱼数目最多的那个池塘钓鱼,并且在数目相同时,要选择池塘编号小的钓鱼。

下面是代码:(还存在小问题,没有AC,仅供思路扩展,稍后还会改进)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Max 30
#define Maxx(a,b) (a)>(b)?(a):(b)
#define Minn(a,b) (a)<(b)?(a):(b)
int f[Max];
int d[Max];
int t[Max];
int ti[Max];
bool trag[Max];
int Sum,h,n;
bool flag;
int main(){
	while(scanf("%d",&n),n){
		scanf("%d",&h);
		h*=60; //转化为分钟
		int i,j; 
		for(i=1;i<=n;i++) //输入f
			scanf("%d",&f[i]);
	    for(i=1;i<=n;i++) //输入d
			scanf("%d",&d[i]);
		for(i=1;i<=n-1;i++){ //输入t
			scanf("%d",&t[i]);
			t[i]*=5;
		}
		Sum=0; //最大钓鱼数目,初始化为0
		memset(ti,0,sizeof(ti)); // 每个池塘钓鱼时间,初始化为0
		memset(trag,false,sizeof(trag)); // 标识已经走过的池塘,初始化为均未走过
	    int now=1,index,tte;
		trag[now]=true; // 初始化第1个池塘走了
		while(h>0){ 
			flag=true; //标识变量,true表示:在原池塘钓鱼,false表示:在其他池塘钓鱼
			int max_s=-1; // 无分钟内的最大钓鱼数目,初始化为负数,注意可能为0情况
			for(i=n;i>=1;i--){ // 循环判断 那个池塘5分钟内钓鱼数目最多
			  if(i>now){ //若编号大于现在池塘,则要求钓鱼数目多于现有池塘,考虑标号小的钓鱼时间尽可能多
				int te=5;
				for(j=i;j>now;j--){ // 计算从第now个池塘到第i个池塘路程所需时间
					if(trag[j]) // 注意条件判断
						break;
					te+=t[j-1];
				}
				if(h>=te){ //若可以到达池塘i,并有5分钟的钓鱼时间
						int temp=f[now],ts=0; //计算在now池塘钓鱼路程花费时间+5分钟钓鱼时间总共可以钓到的鱼数目
					    for(int r=1;r<=te/5;r++){
							if(temp==0)
								break;
							ts+=temp;
							temp-=d[now];
							if(temp<0)
								temp=0;
						} //若在第i个池塘钓鱼数目大于now池塘钓鱼数目,则在第i个池塘钓鱼,注意max_s<=f[i],不是单纯的<号,同样是考虑编号小的钓鱼时间尽可能长
						if(f[i]>ts && max_s<=f[i]){
							max_s=f[i]; //记录最大钓鱼数目池塘数目
							index=i; // 记录相应池塘编号
							tte=te; //记录花费时间
							flag=false; //改变标记
						}
				}
			  }
			  else if(i<now){ // 若小于now,则在第i个池塘钓鱼数目只要大于等于now池塘钓鱼数目即可,注意max_s<=f[i],不是<,同上分析
				  if(f[i]>=f[now] && max_s<=f[i]){
					  max_s=f[i];
					  index=i;
					  tte=5;
					  flag=false;
				  }
			  }
			}
			if(!flag){ // 若交换了位置钓鱼
				h-=tte; // 减去花费的时间
				int min_index=Minn(now,index);
				int max_index=Maxx(now,index);
				for(int r=min_index;r<=max_index;r++) //将经过的池塘编号标记,便于后续处理
					trag[r]=true; 
				f[index]-=d[index]; // 第index池塘的数目要减少
				if(f[index]<0) // 若小于0,则置为0
					f[index]=0;
				ti[index]+=5; // 第index池塘钓鱼时间加上一个5分钟
				Sum+=max_s; // 总数目加上max_s
				now=index; //更换目前所处池塘编号
			}
			else{
				Sum+=f[now]; //总数目增加f[now]
				f[now]-=d[now];// now池塘鱼数目减少
				if(f[now]<0) //若小于0,则置为0
					f[now]=0;
				ti[now]+=5; // 第now池塘钓鱼时间增加5
				h-=5; // 减去花费的5分钟
			}
		}
		printf("%d",ti[1]); // 输出第一个池塘钓鱼时间
		for(i=2;i<=n;i++) // 依次输出第2、3、……、n个池塘钓鱼时间
		printf(", %d",ti[i]); 
		printf("\nNumber of fish expected: %d\n\n",Sum); // 输出最大钓鱼数目
	}
	return 0;
}


 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值