http://poj.org/problem?id=1042
有若干个岛屿,每个岛只和前一个岛有路径相连,路径的长度为 ti 个5 分钟的路程,且该路径是单向的,每个岛上都可以钓鱼,岛 i 初始的 5 分钟能钓到的鱼量为 di , 此后的鱼量以每 5 分钟 di 的速度减少,现在从第一个岛开始,问怎样选择停留的方式,才能使在 h 小时内钓到的鱼量最多?
思路一:动态规划
分组背包问题(详见背包问题九讲)
#pragma warning (disable:4786)
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
#define INF -1111111111
int dp[27][199];
int path[30][199];
struct fishing{
int fi; //初始5分钟内的鱼量
int di; //每5分钟减少的鱼量
int ti; //到前一个岛的距离
}fish[27];
int main(){
int n,h,i,j,k,max,last,last_h;
while( scanf("%d",&n) && n ){
memset( dp, 0, sizeof( dp ) );
不能初始化为0
for( i = 1; i < 27; i ++ )
for( j = 0; j < 198; j ++ )
dp[i][j] = INF;
memset( path, 0, sizeof( path ) );
max = 0,last_h = 0;
scanf( "%d", &h );
h = h * 12;
for( i = 1; i <= n; i ++ ){
scanf( "%d", &fish[i].fi );
}
for( i = 1; i <= n; i ++ ){
scanf( "%d", &fish[i].di );
}
for( i = 2; i <= n; i ++ ){
scanf( "%d", &fish[i].ti );
}
//分组背包
for( i = 1; i <= n; i ++ ){
//算出每个岛能钓鱼的最长时间total_t
int total_t = 0;
if( fish[i].fi > 0 ){
if( fish[i].di > 0 ){
if( fish[i].fi % fish[i].di == 0 )
total_t = fish[i].fi / fish[i].di ;
else total_t = fish[i].fi / fish[i].di + 1;
}
else if( fish[i].di <= 0 )
total_t = h;
}
//动归求出停留在 i 岛的时间的最佳方案
for( k = 0; k <= total_t; k ++ ){
//tmp 表示在 i 岛停留 k 个 5 分钟能钓到的鱼量
int tmp = ( 2 * fish[i].fi - ( k - 1 ) * fish[i].di ) * k / 2;
//tt 表示的是总时间 = 从上一个岛到这个岛花费在路上的时间 + 钓鱼的时间 k
int tt = fish[i].ti + k;
for( j = 0; j <= h; j ++ ){
if( j + tt > h )
break;
///如果 >= 则wa,因为题目要求如果有两个方案的钓到的鱼量是一样的,则选择在越靠前的岛上停留越多时间的方案
if( dp[i - 1][j] + tmp > dp[i][j + tt] ){
dp[i][j + tt] = dp[i - 1][j] + tmp;
//记录,以便恢复在每个岛上停留的时间信息
path[i][j + tt] = j;
if( dp[i][j + tt] > max ){
max = dp[i][j + tt] ;
//最后一个停留的岛
last = i;
//总共用去的时间
last_h = j + tt;
}
}
}
}
}
int result[27],size = 0;
if( max > 0 ){
for( i = last, j = last_h; i > 0; i -- ){
//计算出在每个岛停留的时间
result[size] = j - path[i][j] - fish[i].ti;
j = path[i][j];
size ++;
}
//按题目要求,剩下的时间都算停留在第一个岛上
if( h - last_h > 0 )
result[size - 1] = result[size - 1] + h - last_h;
printf( "%d",result[size - 1] * 5 );
for( i = size - 2; i >= 0; i -- ){
printf( ", %d",result[i] * 5 );
}
}
//所有岛的鱼量都为1的边界情况
else{
if( h - last_h > 0 )
printf("%d",(h - last_h) * 5);
size ++;
}
while( size < n ){
printf( ", 0");
size ++;
}
printf("\n");
printf( "Number of fish expected: %d\n\n",max );
}
return 0;
}
思路二:枚举+贪心
因为顺序经过每个岛一次,故可以这样认为:先花去时间从岛1到岛 i,然后可以在这 i 个岛间 “瞬间移动” ,使用贪心选出此时此刻鱼量最多的 5 分钟,持续选择,直到时间用光为止。枚举 i 从 1 到 能到达的最后一个岛进行上述过程,选出能钓到鱼的最大值。
#pragma warning (disable:4786)
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
int path[30]; // 记录获得最大钓鱼量的方案的每个岛停留的时间
int tmp_path[30]; // 记录大概当前枚举方案的每个岛停留的时间
struct fishing{
int fi; //初始5分钟内的鱼量
int di; //每5分钟减少的鱼量
int tmp; //特定时刻的鱼量
}fish[27];
int main(){
int n,h,i,j,k,max,ti;
int sum[30];
while( scanf("%d",&n) && n ){
memset( path, 0, sizeof( path ) );
memset( sum, 0, sizeof( sum ) );
max = 0;
scanf( "%d", &h );
h = h * 12;
for( i = 1; i <= n; i ++ ){
scanf( "%d", &fish[i].fi );
fish[i].tmp = fish[i].fi;
}
for( i = 1; i <= n; i ++ ){
scanf( "%d", &fish[i].di );
}
for( i = 2; i <= n; i ++ ){
scanf( "%d", &ti );
//sum[i]表示从岛 1 到岛 i 需要的时间
sum[i]= sum[i - 1] + ti;
}
int maxx, tmp_max = 0, time = 0,tmp_time;
//枚举 i 从 1 到 能到达的最后一个岛
for( i = 1; i <= n; i ++ ){
memset( tmp_path, 0, sizeof( tmp_path ) );
tmp_time = 0;
tmp_max = 0;
if( sum[i] >= h )
break;
for( j = 1; j <= i; j ++ ){
fish[j].tmp = fish[j].fi;
}
tmp_time = sum[i];
while( tmp_time < h ){
maxx = 0;
k = -1;
for( j = 1; j <= i; j ++ ){
if( maxx < fish[j].tmp ){
maxx = fish[j].tmp;
k = j;
}
}
if( k != -1 ){
tmp_time = tmp_time + 1;
tmp_max = tmp_max + maxx;
fish[k].tmp = fish[k].tmp - fish[k].di;
tmp_path[k] = tmp_path[k] + 1;
}
else break;
}
if( max < tmp_max ){
max = tmp_max;
for( j = 1; j <= n; j ++ ){
path[j] = tmp_path[j];
}
time = tmp_time;
}
}
if( time < h )
path[1] = path[1] + h - time;
printf( "%d",path[1] * 5 );
for( i = 2; i <= n; i ++ ){
printf( ", %d",path[i] * 5 );
}
printf("\n");
printf( "Number of fish expected: %d\n\n",max );
}
return 0;
}