POJ 1243 One Person(经典DP)
题意:
一个人去猜一个正整数,有G次机会,L个生命值。
每一次猜数,如果猜对,就成功。猜错的话,机会减1,若猜的数大于目标数,另外生命值再减1。机会用完,或者生命值减至-1,表示失败。
现给定G与L,问目标数在什么范围之内,可保证猜的人会胜利。
思路:
做这一题的时候不能被N这个数所迷惑,要用范围的概念去理解。
dp[i][j]表示i次机会,j个生命值,可以覆盖的数值范围
1. 如果j >= i,显然如果失去j个生命值都无法获取正确的数的话,i是没有意义的,所以j = i-1
2. 如果j < i,我们要用最优的策略去走,显然是二分搜索了。下面我们用一张数轴来看待,这个数轴上面不存在所谓的原点。
a. 对于dp[i][j],如果所在的点恰好是目标数,假设此时覆盖了点k
b. 如果所在的点,比目标数要大,则此时的状态变成了dp[i-1][j-1], 相当于点k左边可以多覆盖dp[i-1][j-1]个点
c. 如果所在的点,比目标数要小,则此时的状态变成了dp[i-1][j], 相当于点k右边可以多覆盖dp[i-1][j]个点
所以总共可以覆盖的目标范围就是:dp[i-1][j-1] + 1 + dp[i-1][j]
ps: 看了题解才恍然大悟,对于其中的思想叹为观止,动态规划的精妙之处莫过于此啊
#include <cstdio> #include <cstdlib> #include <cstring> int dp[32][32]; int solve(int i, int j) { if (dp[i][j] != -1) return dp[i][j]; if (j >= i) j = i - 1; dp[i][j] = solve(i-1, j-1) + 1 + solve(i-1, j); return dp[i][j]; } int main() { int G, L; int cases = 0; while (scanf("%d %d", &G, &L) && G) { memset(dp, -1, sizeof(dp)); for (int i = 0; i <= G; ++i) dp[i][0] = i; if (L >= G) L = G - 1; solve(G, L); printf("Case %d: %d\n", ++cases, dp[G][L]); } return 0; }
kedebug
Department of Computer Science and Engineering,
Shanghai Jiao Tong University
E-mail: kedebug0@gmail.com
GitHub: http://github.com/kedebug
-------------------------------------------------------