纸牌博弈问题

题目:有一个整型数组A,代表数值不同的纸牌排成一条线。玩家a和玩家b依次拿走每张纸牌,规定玩家a先拿,玩家b后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家a和玩家b都绝顶聪明,他们总会采用最优策略。请返回最后获胜者的分数。给定纸牌序列A及序列的大小n,请返回最后分数较高者得分数(相同则返回任意一个分数)。

测试样例:
[1,2,100,4],4
返回:101

解析:

a和b都是绝顶聪明,他们每次拿元素时,肯定是按对自己最有力的方式拿。该题目先由最普通的递归解法,然后进行优化,到动态规划。

递归方式,对数组arr,元素数为n。

F(arr, l , r)表示对于数组arr,元素从l到r,先拿可以达到的最大分数;

S(arr, l, r)表示对于数组arr, 元素从l到r,后拿可以达到的最大分数。

对于F(arr, l, r),先拿时,有两种拿法,拿第一个arr[l],或最后一个arr[r];如果拿arr[l],那么剩余的arr[l+1,....r]能拿到的最大分数为S(arr, l+1, r),分数为arr[l] +S(arr, l+1, r); 如果拿arr[r],剩余的arr[l, ...r-1]能拿到的最大分数为S(arr, l, r-1),分数为arr[r] + S(arr, l, r-1),因为对于先拿后剩余的数组,当前人再拿的话是后拿的,然后取这两种拿法较大的分数。

对于S(arr, l, r),如果前一个人先拿了arr[l],则后拿的分数为F(arr, l+1, r),如果前一个人先拿了arr[r],则后拿的分数为F(arr, l, r-1),因为对于剩余的元素来说,你是先拿的,取两种方式的较小值才是S的值。(为什么取较小值,而不是较大值?因为a和b都是绝顶聪明人,你是在另一个绝顶聪明人之后才拿的,他给你剩下的肯定是较坏的情况

递归实现的方式如下,可以再进行动态规划方式的优化,接下来再讲。

[cpp]  view plain  copy
  1. /* 返回较大值 */  
  2. int Max(int a, int b)  
  3. {  
  4.     return ((a > b) ? a : b);  
  5. }  
  6.   
  7. /* 返回较小值 */  
  8. int Min(int a, int b)  
  9. {  
  10.     return ((a < b) ? a : b);  
  11. }  
  12.   
  13. /* 对数组arr,从l到r元素,先拿的最大分数 */  
  14. int F(int arr[], int l, int r)  
  15. {  
  16.     if (l == r)  
  17.     {  
  18.         return arr[l];  
  19.     }  
  20.     return Max(arr[l] + S(arr, l+1, r), arr[r] + S(arr, l, r-1));  
  21. }  
  22.   
  23. /* 对数组arr,从l到r元素,后拿的最大分数 */  
  24. int S(int arr[], int l, int r)  
  25. {  
  26.     if (l == r)  
  27.     {  
  28.         return 0;  
  29.     }  
  30.     return Min(F(arr, l+1, r), F(arr, l, r-1));  
  31. }  
  32.   
  33. int FindWinnerScore(int arr[], int l, int r)  
  34. {  
  35.     int A_score = 0;  
  36.     int B_score = 0;  
  37.     A_score = F(arr, l, r);  
  38.     B_score = S(arr, l, r);  
  39.     return ((A_score > B_score) ? A_score : B_score);  
  40. }  


态规划定义了两个表F和S,F[i][j]表示arr[i...j]先拿的最大分数,S[i][j]表示arr[i...j]后拿的最大分数。最终比较F[0][n-1]和S[0][n-1]的值,返回较大的即可。

在遍历填写两个表之前,我们可以对表进行初始化。初始化后,可以按列进行遍历,行从最后一个开始,因为每个F[i][j]和S[i][j]的值都是由F[i+1][j]、F[i][j-1]或S[i+1][j]、S[i][j-1]得到的。


动态规划的代码如下:

[cpp]  view plain  copy
  1. int DP(int arr[], int n)  
  2. {  
  3.     int i = 0;  
  4.     int j = 0;  
  5.     int** F = NULL;  
  6.     int** S = NULL;  
  7.     F = (int**)malloc(sizeof(int*)*n);  
  8.     S = (int**)malloc(sizeof(int*)*n);  
  9.   
  10.   
  11.     for (i = 0; i < n; i++)  
  12.     {  
  13.         F[i] = (int*)malloc(sizeof(int)*n);  
  14.         S[i] = (int*)malloc(sizeof(int)*n);  
  15.     }  
  16.   
  17.   
  18.     /* 初始化 */  
  19.     F[0][0] = arr[0];  
  20.     S[0][0] = 0;  
  21.     F[n-1][n-1] = arr[n-1];  
  22.     S[n-1][n-1] = 0;  
  23.     for (i = 1; i < n; i++)  
  24.     {  
  25.         F[i][0] = 0;  
  26.         S[i][0] = 0;  
  27.     }  
  28.     for (i = 0; i < n-1; i++)  
  29.     {  
  30.         F[n-1][i] = 0;  
  31.         S[n-1][i] = 0;  
  32.     }  
  33.   
  34.     for (j = 1; j < n; j++)  
  35.     {  
  36.         for (i = n-2; i >= 0; i--)  
  37.         {  
  38.             if (i <= j)  
  39.             {  
  40.                 F[i][j] = Max(arr[i] + S[i+1][j], arr[j] + S[i][j-1]);  
  41.                 S[i][j] = Min(F[i+1][j], F[i][j-1]);  
  42.             }  
  43.             else  
  44.             {  
  45.                 F[i][j] = 0;  
  46.                 S[i][j] = 0;  
  47.             }  
  48.         }  
  49.     }  
  50.   
  51.     return Max(F[0][n-1], S[0][n-1]);  
  52. }  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值