USACO A Game解题报告

典型DP问题

问题描述:
给出一个有n个数序列a1,a2,a3...an,做一个博弈问题.
两人轮流重序列的两端取走一个数,在第二个人以最佳策略取数的情况下求第一个人取数的和的最大值.

问题分析:
两个人都以最佳策略取数,那么这个最佳策略就是一个一个最优解,考虑有n-2个数的序列,它的最优解为x,给这个序列再加上2个数,an-1,an,取这两个数的较大者加上x,就是一个新的最优解.反过来说,每个最优解,它减去序列中的一个数,都是另一种情况的最优解,所以最优子结构得证.
一个最优解可以加上数变成新的最优解,所以重叠子问题得证.可以用动态规划求解.

符号及变量说明:
记a为数的序列,ai为序列中第i个数.
记sum(i,j)为序列中第i个数到第j个数的和.
记f(i,j)为序列中由i开始到第j个数按照游戏规则能取得到的最大值.

模型的建立:
设得到一个子序列ai,ai+1,ai+2,...,aj-2,aj-1,aj;
由于后一个人按最佳策略取数,所以他必将取一个最大值f(i+1,j)或f(i,j-1),那么此时取数的人以后取得的数将是sum(i+1,j)-f(i+1,j)或是sum(i,j-1)-f(i,j-1),此时取数的人按最佳策略取数,他会取ai或者aj使得sum(i+1,j)-f(i+1,j)+ai和sum(i,j-1)-f(i,j-1)+aj二者能有较大值.这样从游戏的结束向游戏的开始推,可以得到总的最优解.

代码如下

#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
int a[101]={0};
int sum[101][101]={0};         //子段和
int max_sum[101][101]={0};     //记录各分段的最优
ifstream fin("game1.in");
ofstream fout("game1.out");
int main()
{
    int N;
    int i,j,k;
    fin>>N;
    for(i=1;i<=N;i++)
    fin>>a[i];
    for(int i=1;i<=N;i++)
    {
        sum[i][i]=max_sum[i][i]=a[i];
    }
    for(i=1;i<=N;i++)
    for(j=i+1;j<=N;j++)          //初始化子段和
    {
        sum[i][j]=sum[i][j-1]+a[j];
    }
    for(i=1;i<=N;i++)
    for(j=1;j<=N;j++)
    for(k=j+i;k<=N;k++)
    {
        max_sum[j][k]=max(a[j]+sum[j+1][k]-max_sum[j+1][k],a[k]+sum[j][k-1]-max_sum[j][k-1]);
    }
    fout<<max_sum[1][N]<<" "<<sum[1][N]-max_sum[1][N]<<endl;
    return 0;
}

你可能感兴趣的:(游戏)