poj1042 动态规划 贪心

博客介绍了在解决poj1042问题时,作者从动态规划的角度出发,遇到困难并最终采用贪心算法解决的过程。文章详细讨论了动态规划的状态转移方程,并提供了贪心算法的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:一个人去钓鱼,有n个池塘,第i个池塘初始时可以钓到F[i]条鱼,但是每过一个时间单位(这里是5分钟),池塘里的鱼就会减少d[i]条,从第i个池塘行走到第i+1个池塘需要花t[i]个时间单位,问这个人在规定的h小时内最多可以钓到多少鱼,以及在每个池塘待多长时间(如果有几种方案,尽量使待在前面的池塘的时间久一些)。


最近在做一些动态规划的题目,发现动态规划的题目很难,特别是如何设计状态和状态转移方程。这题自己也想了好久,一直没想到神马好的思路。

最初的想法是:设dp[i][j] 表示在前i个池塘中,在规定的j个时间单位内可以钓到的最多的鱼数目,但是如何将第i个池塘的时间j内的钓鱼数和前一个池塘联系起来,这之间费了好多周折,开始的时候思路也一直不是很清晰,认为只要max(dp[i-1][j-t[i-1]]+f[i](其中f[i] = f[i]-d[i])。

但是觉得怎么都说不通,一是算dp[i]的时候,必须是从dp[i-1]过渡过来的,所以其中肯定消耗了t[i-1]的时间。二是不知道啥时候才从dp[i-1]过渡过来,如果第i-1个池塘没有鱼,那么dp[i]还是从dp[i-2]过渡过来的。

一直没想到怎么弄转移方程,哎,自己的思维能力还是很差,需慢慢锻炼。万般无奈,只得在网上找解题报告,看了网上基本都是用贪心+枚举来做,用dp做的不是很多。其实网上大妞的dp思路跟我的有点像,但是自己一直卡在何时从i-1过渡到i的问题,看了牛人的状态转移方程瞬间明白。

dp[i][j] = max{ dp[i-1][k] + sum(j-k-t[i-1]) | 0<=k<=j-t[i-1] },一个k搞定一切,不知道从哪开始过渡,那就枚举呗,为啥自己脑子这么不好使(牛人别笑话)。

这个式子的意义就是前i个池塘在时间j内可以钓到的最多鱼数 = 前i-1个池塘在时间k内钓到的最多鱼数 +  j-k-t[i-1]在池塘i内钓到的鱼总数。为了求在每个池塘钓鱼的时间,还是定义一个数组time保存钓鱼时间。

这个题目还有些需要注意的地方:初始化问题,dp初始化为-1,表示不可达。dp[0]初始化为0。

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <math.h>  
  4.   
  5. #define N 26  
  6. #define H 16*60/5+1  
  7.   
  8. int dp[N][H];  
  9. int time[N][H];  
  10.   
  11. int f[N];  
  12. int d[N];  
  13. int t[N];  
  14.   
  15. inline int max(const int a, const int b)  
  16. {  
  17.     return a > b ? a : b;  
  18. }  
  19.   
  20. inline int Sum(int i, int k)  
  21. {  
  22.     int sum;  
  23.     int t;  
  24.     static int ss = 0;  
  25.     if (d[i] > 0)  
  26.     {  
  27.         t = f[i]/d[i]+1;  
  28.         if (k > t)  
  29.             //sum = (t-1)*f[i] - (t-1)*(t-2)*d[i]/2+f[i]-(t-1)*d[i];  
  30.             sum = t*f[i] - t*(t-1)*d[i]/2;  
  31.         else  
  32.             sum = k*f[i] - k*(k-1)*d[i]/2;  
  33.     }  
  34.     else  
  35.         sum = k * f[i];  
  36.     return sum;  
  37. }  
  38.   
  39. void printPath(int s, int h)  
  40. {  
  41.     if (s == 1)  
  42.     {  
  43.         printf("%d, ", h * 5);  
  44.         return;  
  45.     }  
  46.       
  47.     printPath(s-1, h - time[s][h] - t[s-1]);  
  48.     printf("%d, ", time[s][h] * 5);  
  49. }  
  50.   
  51. void solve(int n, int h)  
  52. {  
  53.     int i, j, k;  
  54.     for (i = 0; i <= n; i++)  
  55.     {  
  56.         for (j = 0; j <= h; j++)  
  57.         {  
  58.             if (i == 0)  
  59.                 dp[i][j] = 0;  
  60.             else  
  61.                 dp[i][j] = -1;  
  62.         }  
  63.     }  
  64.     memset(time, -1, sizeof(time));  
  65.     int cum = 0;  
  66.     int ans = -1;  
  67.     int s;  
  68.     for (i = 1; i <= n; i++)  
  69.     {  
  70.         cum += t[i-1];  
  71.         for (j = cum; j <= h; j++)  
  72.         {  
  73.             for (k = cum - t[i-1]; k <= j - t[i-1]; k++)  
  74.             {  
  75.                 int sum = Sum(i, j - k - t[i-1]);  
  76.                 if (dp[i-1][k] != -1 && dp[i][j] <= dp[i-1][k] + sum)<span style="white-space:pre">  </span>//这里注意是<= 等号不能少  
  77.                 {  
  78.                     dp[i][j] = dp[i-1][k] + sum;  
  79.                     time[i][j] = j - k - t[i-1];   
  80.                 }  
  81.             }  
  82.         }  
  83.         if (ans < dp[i][h])  
  84.         {  
  85.             s = i;  
  86.             ans = dp[i][h];  
  87.         }  
  88.     }  
  89.       
  90.     if (s > 1)  
  91.     {  
  92.         printPath(s - 1, h - time[s][h] - t[s-1]);  
  93.         printf("%d", 5 * time[s][h]);  
  94.     }  
  95.     else  
  96.         printf("%d", 5 * h);  
  97.     if (s < n)  
  98.     {  
  99.         for (i = s + 1; i <= n; i++)  
  100.             printf(", 0");   
  101.     }  
  102.     printf("\n");  
  103.     printf("Number of fish expected: %d\n\n", ans);  
  104. }  
  105.   
  106. int main(void)  
  107. {  
  108.     int i, j;  
  109.     int n;  
  110.     int h;  
  111.     while (scanf("%d", &n) && n)  
  112.     {  
  113.         scanf("%d", &h);  
  114.         h = h * 12;  
  115.         for (i = 1; i <= n; i++)  
  116.             scanf("%d", &f[i]);  
  117.         for (i = 1; i <= n; i++)  
  118.             scanf("%d", &d[i]);  
  119.         t[0] = 0;  
  120.         for (i = 1; i < n; i++)  
  121.             scanf("%d", &t[i]);  
  122.         solve(n, h);  
  123.     }  
  124.   
  125.     return 0;  
  126. }  


上述dp耗时很大,花了1700MS,险过,听说贪心时间复杂度很低,于是在网上直接看了人家如何贪心的(菜鸟以后还是自己多思考,少看人间的解题报告)。

具体贪心策略如下:

由于钓鱼者为了尽可能多的利用时间,所以我们需要最小化钓鱼者在从一个池塘走到另一个池塘的时间,这就产生了贪心的思想,即钓鱼者从第i个池塘钓完鱼后,直接前往下一个池塘,以后也不会在返回到第i个及其小于i的池塘去钓鱼了,这样就节省了大量的路途时间,把这些时间转化为钓鱼时间,岂不美哉。

但是钓鱼者发现从第i个池塘以后去钓鱼没有在之前钓鱼多,他肯定就不走下去了。所以,这就产生了一个问题,钓鱼究竟最后停在那个池塘了呢?这个问题和上面的动态规划算法中的一个细节很相似,上面动态规划算法一个细节就是钓鱼者在第i个池塘的时候,总时间为j的情况下,如何算dp[i][j]的问题,上面的解法是枚举钓鱼者离开第i-1个池塘的时间。同理,这里也可以枚举钓鱼者可以在多少个池塘钓到最多的鱼。

代码如下:

[cpp]  view plain copy
  1. /****************** greedy algorithm *****************************/  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. #include <math.h>  
  5.   
  6. #define N 26  
  7. #define H 16*60/5+1  
  8.   
  9. int dp[N];  
  10. int time[N][N];  
  11.   
  12. int f[N];  
  13. int fc[N];  
  14. int d[N];  
  15. int t[N];  
  16.   
  17. int Findmax(int *arr, int s, int e)  
  18. {  
  19.     if (s > e)  
  20.         return -1;  
  21.     int max_index = s;  
  22.     for (int i = s + 1; i <= e; i++)  
  23.     {  
  24.         if (arr[max_index] < arr[i])  
  25.         {  
  26.             max_index = i;  
  27.         }  
  28.     }  
  29.     return max_index;  
  30. }  
  31.   
  32. void solve(int n, int h)  
  33. {  
  34.     int i, j, k;  
  35.     //for (i = 1; i <= n; i++)  
  36.     //{  
  37.     //  for (j = 1; j <= n; j++)  
  38.     //  {  
  39.     //      time[i][j] = 0;  
  40.     //  }  
  41.     //}  
  42.   
  43.     dp[0] = 0;  
  44.     int total_time = 0;  
  45.     int lake_id = 0;  
  46.     // enumeration the last lake the people fishing  
  47.     for (i = 1; i <= n; i++)  
  48.     {  
  49.         dp[i] = 0;  
  50.         total_time = h;  
  51.         for (j = 1; j <= i; j++)  
  52.         {  
  53.             fc[j] = f[j];  
  54.             total_time -= t[j-1];   
  55.             time[i][j] = 0;  
  56.         }  
  57.         for (; j <= n; j++)  
  58.             time[i][j] = 0;  
  59.         // starting fish  
  60.         while (total_time > 0)  
  61.         {  
  62.             int index = Findmax(fc, 1, i);  
  63.             dp[i] += fc[index];  
  64.             time[i][index]++;  
  65.             fc[index] -= d[index];  
  66.             if (fc[index] < 0)  
  67.                 fc[index] = 0;  
  68.             total_time--;  
  69.         }  
  70.           
  71.         if (dp[lake_id] < dp[i] || lake_id == 0 && dp[lake_id] <= dp[i])  
  72.             lake_id = i;  
  73.     }  
  74.   
  75.     for (i = 1; i < n; i++)  
  76.     {  
  77.         printf("%d, ", time[lake_id][i] * 5);  
  78.     }  
  79.     printf("%d\n", time[lake_id][n] * 5);  
  80.       
  81.     printf("Number of fish expected: %d\n\n", dp[lake_id]);  
  82. }  
  83.   
  84. int main(void)  
  85. {  
  86.     int i, j;  
  87.     int n;  
  88.     int h;  
  89.     while (scanf("%d", &n) && n)  
  90.     {  
  91.         scanf("%d", &h);  
  92.         h = h * 12;  
  93.         for (i = 1; i <= n; i++)  
  94.             scanf("%d", &f[i]);  
  95.         for (i = 1; i <= n; i++)  
  96.             scanf("%d", &d[i]);  
  97.         t[0] = 0;  
  98.         for (i = 1; i < n; i++)  
  99.             scanf("%d", &t[i]);  
  100.         solve(n, h);  
  101.     }  
  102.   
  103.     return 0;  
  104. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值