最近在做一些动态规划的题目,发现动态规划的题目很难,特别是如何设计状态和状态转移方程。这题自己也想了好久,一直没想到神马好的思路。
最初的想法是:设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。
- #include <stdio.h>
- #include <string.h>
- #include <math.h>
- #define N 26
- #define H 16*60/5+1
- int dp[N][H];
- int time[N][H];
- int f[N];
- int d[N];
- int t[N];
- inline int max(const int a, const int b)
- {
- return a > b ? a : b;
- }
- inline int Sum(int i, int k)
- {
- int sum;
- int t;
- static int ss = 0;
- if (d[i] > 0)
- {
- t = f[i]/d[i]+1;
- if (k > t)
- //sum = (t-1)*f[i] - (t-1)*(t-2)*d[i]/2+f[i]-(t-1)*d[i];
- sum = t*f[i] - t*(t-1)*d[i]/2;
- else
- sum = k*f[i] - k*(k-1)*d[i]/2;
- }
- else
- sum = k * f[i];
- return sum;
- }
- void printPath(int s, int h)
- {
- if (s == 1)
- {
- printf("%d, ", h * 5);
- return;
- }
- printPath(s-1, h - time[s][h] - t[s-1]);
- printf("%d, ", time[s][h] * 5);
- }
- void solve(int n, int h)
- {
- int i, j, k;
- for (i = 0; i <= n; i++)
- {
- for (j = 0; j <= h; j++)
- {
- if (i == 0)
- dp[i][j] = 0;
- else
- dp[i][j] = -1;
- }
- }
- memset(time, -1, sizeof(time));
- int cum = 0;
- int ans = -1;
- int s;
- for (i = 1; i <= n; i++)
- {
- cum += t[i-1];
- for (j = cum; j <= h; j++)
- {
- for (k = cum - t[i-1]; k <= j - t[i-1]; k++)
- {
- int sum = Sum(i, j - k - t[i-1]);
- if (dp[i-1][k] != -1 && dp[i][j] <= dp[i-1][k] + sum)<span style="white-space:pre"> </span>//这里注意是<= 等号不能少
- {
- dp[i][j] = dp[i-1][k] + sum;
- time[i][j] = j - k - t[i-1];
- }
- }
- }
- if (ans < dp[i][h])
- {
- s = i;
- ans = dp[i][h];
- }
- }
- if (s > 1)
- {
- printPath(s - 1, h - time[s][h] - t[s-1]);
- printf("%d", 5 * time[s][h]);
- }
- else
- printf("%d", 5 * h);
- if (s < n)
- {
- for (i = s + 1; i <= n; i++)
- printf(", 0");
- }
- printf("\n");
- printf("Number of fish expected: %d\n\n", ans);
- }
- int main(void)
- {
- int i, j;
- int n;
- int h;
- while (scanf("%d", &n) && n)
- {
- scanf("%d", &h);
- h = h * 12;
- for (i = 1; i <= n; i++)
- scanf("%d", &f[i]);
- for (i = 1; i <= n; i++)
- scanf("%d", &d[i]);
- t[0] = 0;
- for (i = 1; i < n; i++)
- scanf("%d", &t[i]);
- solve(n, h);
- }
- return 0;
- }
上述dp耗时很大,花了1700MS,险过,听说贪心时间复杂度很低,于是在网上直接看了人家如何贪心的(菜鸟以后还是自己多思考,少看人间的解题报告)。
具体贪心策略如下:
由于钓鱼者为了尽可能多的利用时间,所以我们需要最小化钓鱼者在从一个池塘走到另一个池塘的时间,这就产生了贪心的思想,即钓鱼者从第i个池塘钓完鱼后,直接前往下一个池塘,以后也不会在返回到第i个及其小于i的池塘去钓鱼了,这样就节省了大量的路途时间,把这些时间转化为钓鱼时间,岂不美哉。
但是钓鱼者发现从第i个池塘以后去钓鱼没有在之前钓鱼多,他肯定就不走下去了。所以,这就产生了一个问题,钓鱼究竟最后停在那个池塘了呢?这个问题和上面的动态规划算法中的一个细节很相似,上面动态规划算法一个细节就是钓鱼者在第i个池塘的时候,总时间为j的情况下,如何算dp[i][j]的问题,上面的解法是枚举钓鱼者离开第i-1个池塘的时间。同理,这里也可以枚举钓鱼者可以在多少个池塘钓到最多的鱼。
代码如下:
- /****************** greedy algorithm *****************************/
- #include <stdio.h>
- #include <string.h>
- #include <math.h>
- #define N 26
- #define H 16*60/5+1
- int dp[N];
- int time[N][N];
- int f[N];
- int fc[N];
- int d[N];
- int t[N];
- int Findmax(int *arr, int s, int e)
- {
- if (s > e)
- return -1;
- int max_index = s;
- for (int i = s + 1; i <= e; i++)
- {
- if (arr[max_index] < arr[i])
- {
- max_index = i;
- }
- }
- return max_index;
- }
- void solve(int n, int h)
- {
- int i, j, k;
- //for (i = 1; i <= n; i++)
- //{
- // for (j = 1; j <= n; j++)
- // {
- // time[i][j] = 0;
- // }
- //}
- dp[0] = 0;
- int total_time = 0;
- int lake_id = 0;
- // enumeration the last lake the people fishing
- for (i = 1; i <= n; i++)
- {
- dp[i] = 0;
- total_time = h;
- for (j = 1; j <= i; j++)
- {
- fc[j] = f[j];
- total_time -= t[j-1];
- time[i][j] = 0;
- }
- for (; j <= n; j++)
- time[i][j] = 0;
- // starting fish
- while (total_time > 0)
- {
- int index = Findmax(fc, 1, i);
- dp[i] += fc[index];
- time[i][index]++;
- fc[index] -= d[index];
- if (fc[index] < 0)
- fc[index] = 0;
- total_time--;
- }
- if (dp[lake_id] < dp[i] || lake_id == 0 && dp[lake_id] <= dp[i])
- lake_id = i;
- }
- for (i = 1; i < n; i++)
- {
- printf("%d, ", time[lake_id][i] * 5);
- }
- printf("%d\n", time[lake_id][n] * 5);
- printf("Number of fish expected: %d\n\n", dp[lake_id]);
- }
- int main(void)
- {
- int i, j;
- int n;
- int h;
- while (scanf("%d", &n) && n)
- {
- scanf("%d", &h);
- h = h * 12;
- for (i = 1; i <= n; i++)
- scanf("%d", &f[i]);
- for (i = 1; i <= n; i++)
- scanf("%d", &d[i]);
- t[0] = 0;
- for (i = 1; i < n; i++)
- scanf("%d", &t[i]);
- solve(n, h);
- }
- return 0;
- }