博弈dp 以I Love this Game! POJ - 1678 为例

写在前面的话

知识基础:一些基础的博弈论的方法,动态规划的一些知识

前言:博弈论就是一些关于策略或者游戏之间的最优解,动态规划就是对于一些状态之间转移的一些递推式(or 递归),dp分为很多很多种,比如状压dp我感觉其实就是一种暴力,数位dp也可以用记忆化搜索的形式解决。可见动态规划其实是解决一些不能快速解决,不能以O(1)方法解决的问题,而博弈之中,经常会出现一些O(1)方法解决的策略,或者O(n),可见博弈dp它有其不确定性(或者说有不能直接解决的方案)所以需要在博弈的基础上加上dp。举一个很简单的例子,但这个例子没有什么实质性的意义,知识为了帮助理解博弈dp,比如有一个01串,两个人轮流取其中的数字,要求下一次取的那个的下标必须比前一个取的下标要大,比如第一个人取第一个,第二个人只能取(2-n)个。很简单吧,然后问你两个人最多可以加起来取多少个1。你肯定会说这要dp啥,这要博弈啥,我就举个例子嘛。博弈在这里面就是每个人都取前一次取的那个的后面的那个是不是就是最优的啦,dp就当作枚举第一个人拿去的第一个数的下标是几,然后可以拿到多少个1,比如dp[m]就是第一个人取第m个可以拿到的最多的1的个数。

现在就来看poj-1678这道题

题意:2个人轮流拿数组里面的一个数,且要比另外一个人上一次拿的数大[a,b],第一次拿要拿[a,b]范围的数,求第一个人拿的总和比第二个人的总和最多大多少

思考:很明显,他们拿的肯定是一个比一个大(题目里说了0<a<b),所以先排个序那就是肯定是按照从左往右取的对吧。

看个样例:

6 1 2 

1 3 -2 5 -3 6

把它排序之后就是:

6 1 2

-3 -2 1 3 5 6

很明显第一个人只能取1,然后第二个人取3,第一个人再取5,第二个人再取6,那么差值就是1+5-3-6 = -3

看了这个样例你肯定会感觉看了和没看一样,因为这里取的情况被锁死了,那我们来看看下一个我瞎造的样例

8 1 10

1 2 3 4 5 6 7 8

假设第一个人先后取1 3 5 7,第二个人先后取2 4 6 8,这个是符合题意的,然而差值是-4,甚至是player2赢了

假设第一个人先后取3 5 7,第二个人先后取4 6 8,这个也是符合题意的,然而差值是-3,还是player2赢了

假设第一个人先后取4 6 8,第二个人先后取5 7,这个也是符合题意的,差值为6,player1赢了

最优解就是第一个人直接就是取8,那么第二个人取不了了,差值为8,player1赢了,甚至没有给player2一个后手,太狠了

所以发现问题了吗,player1第一次取的那个数字是什么是影响结果的,同时,假如第一个人选1,第二个人直接选8也是可行,可见第二个人的选法也是影响结果的,所以这就是一个dp,或者可以说是一个记忆化的搜索。

看看代码就会知道,soga

 

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<cmath>
#include<stack>
#include<cstdlib>
#include <vector>
#include <set>
#include<queue>

using namespace std;

#define ll long long
#define llu unsigned long long
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
const int maxn = 1e4+5;
int num[maxn];
int n,a,b;
int dp[maxn];

void init()
{
    for(int i=0;i<n;i++)
        dp[i] = -INF;
}
int DP(int m)
{
    if(dp[m] != -INF)   //dp[m]表示如果先手取了第m个数,可以达到的最大的分差
          return dp[m]; //如果第m个数已经取了,直接就return,这个不加可能会t
    int ans = INF;
    for(int i=m+1;i<n;i++)
        if(num[i]-num[m] >= a && num[i]-num[m] <= b)
            ans = min(ans,num[m] - DP(i));  //dp就是枚举最后一个状态,如果最后一个状态已经涵盖了,那么之前的所有状态都涵盖了
    if(ans == INF)//取不了了
        return dp[m] = num[m];
    return  dp[m] = ans;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&a,&b);
        init();
        for(int i=0;i<n;i++)
            scanf("%d",&num[i]);
        sort(num,num+n);
        int ans = -INF;
        for(int i=0;i<n;i++)    //枚举第一个人拿的每一个数
            if(num[i] >= a && num[i] <= b)
                ans = max(ans,DP(i));
        printf("%d\n",ans == -INF ? 0 : ans);
    }
}

 

posted @ 2019-02-26 21:34  千摆渡Qbd  阅读(250)  评论(0编辑  收藏  举报