区间dp||记忆化搜索 Game of Sum UVA

来源:互联网 发布:淘宝龙瞎皮肤多少钱 编辑:程序博客网 时间:2024/05/17 03:43

最近的个人赛dp
题目链接
This is a two player game. Initially there are n integer numbers in an array and players A and B get
chance to take them alternatively. Each player can take one or more numbers from the left or right end
of the array but cannot take from both ends at a time. He can take as many consecutive numbers as
he wants during his time. The game ends when all numbers are taken from the array by the players.
The point of each player is calculated by the summation of the numbers, which he has taken. Each
player tries to achieve more points from other. If both players play optimally and player A starts the
game then how much more point can player A get than player B?
Input
The input consists of a number of cases. Each case starts with a line specifying the integer n (0 <
n ≤ 100), the number of elements in the array. After that, n numbers are given for the game. Input is
terminated by a line where n = 0.
Output
For each test case, print a number, which represents the maximum difference that the first player
obtained after playing this game optimally.
Sample Input
4
4 -10 -20 7
4
1 2 3 4
0
Sample Output
7
10
题目大意:有n个石头排成一条排,然后有两个人来玩游戏, 每个人每次可以从两端(左或右)中的任意一端取走若干个石头(获得价值为取走石头之和), 但是他取走的方式一定要让他在游戏结束时价值尽量的高,两个人都很聪明,所以每一轮两人都将按照对自己最有利的方法去取数字,请你算一下在游戏结束时,先取数的人价值与后取数人价值之差。
解题思路:dp函数返回的是i到j玩家1可以取得最大值。

#include<stdio.h>  #include<stdlib.h>  #include<algorithm>  #include<string.h>  using namespace std;  int S[105], A[105], d[105][105], vis[105][105], n;  int dp(int i, int j) { //dp函数返回的是i到j玩家1可以取得最大值      if (vis[i][j]) return d[i][j];      vis[i][j] = 1;      int m = 0;      for (int k = i + 1; k <= j; k++) {          m = min(m, dp(k, j));      }      for (int k = i; k < j; k++) {          m = min(m, dp(i, k));      }      d[i][j] = S[j] - S[i - 1] - m;      return d[i][j];  }  int main() {      while (scanf("%d", &n), n) {          memset(vis, 0, sizeof(vis));          S[0] = 0;          for (int i = 1; i <= n; i++) {              scanf("%d", &A[i]);              S[i] = S[i - 1] + A[i];          }          printf("%d\n", 2 * dp(1, n) - S[n]); //dp(1, n) - (S[n] - dp(1, n))      }         return 0;  }  

参考博客
java版:

总和是一定的,所以一个人的得分越高,另一个人的得分越低。所以我们可以分析,另一个人在所能取得所有最优选择中得分最低的分数 m ,sum-m即为我的最高得分。用d(i,j) 表示原 序列 [i...j] 在双方都采取最优的策略的情况下,先手得分的最大值。对方肯定有一种可能是得0分,因为先手(先手是相对的,并不就是指第一个取的人)可以取完所有的。d(i,j) = sum(i,j) – min{ d(i+1,j), d(i+2,j), … , d(j,j) , d(i,j-1), d(i,j-2), … , d(i,i) , 0 }下面是Java记忆化搜索的代码:import java.io.BufferedInputStream;import java.util.Arrays;import java.util.Scanner;public class Uva_19461 {    static int n, sum[] = new int[101];    static int dp[][] = new int[101][101];    static boolean vis[][] = new boolean[101][101];    //记忆化搜索    static int dp(int a,int b){        if(vis[a][b]) return dp[a][b];        vis[a][b] = true;        int m = 0;        for(int k=a+1; k<=b; k++) m = Math.min(m, dp(k,b) ); //枚举右半部分        for(int k=a; k<b; k++) m = Math.min(m, dp(a,k) );//枚举左半部分        dp[a][b] =  sum[b] - sum[a-1] - m ;        return dp[a][b];    }    public static void main(String[] args) {        Scanner s = new Scanner(new BufferedInputStream(System.in));        while(true){            n = s.nextInt();            if(n == 0) break;            sum[0] = 0;            Arrays.fill(vis[0],false);            for(int i=1; i<=n; i++){                sum[i] = sum[i-1] +  s.nextInt();                Arrays.fill(vis[i],false);            }            System.out.println(2*dp(1,n) - sum[n]);        }    }}

3:dp版

题意:有n个数排成一行,现在A和B两人从两端取任意个数(每次至少取一个),直到取完所有的数,A先取,求A取得数的和比B大多少?思路:区间DP,dp[i][j] 表示区间i~j A取得数的和比B大多少,那么可以这样分析:对于区间i~j 对于先手A可以先取sum[k]-sum[i-1]或sum[j]-sum[k](只能从两端取),然后该B取了,即对于区间k+1~j和i~k 这样即为DP转换为B比A大多少了,所以状态转移方程为 dp[i][j]=max(dp[i][j],max(sum[k]-sum[i-1]-dp[k+1][j],sum[j]-sum[k]-dp[i][k]));
#include <iostream>#include <algorithm>#include <cstdio>#include <cstring>using namespace std;int sum[105];int dp[105][105]; ///A比B大多少?int main(){    int T,Case=1;    int n;    while(cin>>n)    {        if(n==0)break;           sum[0]=0;        for(int i=1; i<=n; i++)        {            scanf("%d",&sum[i]);            sum[i]+=sum[i-1];        }        memset(dp,0,sizeof(dp));        for(int i=1; i<=n; i++)            dp[i][i]=sum[i]-sum[i-1];        for(int len=1; len<n; len++)        {            for(int i=1; i<=n; i++)            {                if(i+len>n) break;                dp[i][i+len]=sum[i+len]-sum[i-1];                for(int k=i; k<i+len; k++)                {                    dp[i][i+len]=max(dp[i][i+len],max(sum[k]-sum[i-1]-dp[k+1][i+len],sum[i+len]-sum[k]-dp[i][k]));                }            }        }        printf("%d\n",dp[1][n]);    }}

4:参考一下leaderboard上的优化写法

区间dp