[基础博弈论练习题](性质(神仙)题 + dp)

(基础个鬼)

题意:小A和小B在玩游戏,假设一个长度为n的数组,小A想到一个数x,小B每次猜一个数y让小A回答x >= y?小A最多可以撒一次慌,小B希望最小化询问的次数来确定x,小A希望最大化小B问的次数.对于每个u,假设小B第一次询问的是u,且小A回答是(有可能撒谎),且接下来两者都采取最优策略的情况下,你需要求出接下来至少还要几轮才能确定答案.

n <= 2e3

发现是否撒谎这一点特别难处理.我们考虑改写游戏来处理是否撒谎这个问题,设a[y]表示如果小A想的是y,那么小A还能撒谎多少次.显然初始a[y] = 1且当a[y] < 0时,y便不可能是小A想的数.考虑每次回答,与小A回答向左的a[y]就是减少1(即如果回答x >= y为真则任意u < y 有 a[u]--).考虑小B当且仅当仅存在一个a[y] >= 0时,小B可以确定答案.

容易发现只有非负的a我们需要处理,又因为每次减少的都是前缀一段,或者后缀一段.所以我们发现>=0的a一定满足(就是把所有小于0的a都删掉,重新组成一段连续的)u个0,v个1,然后w个0

于是我们可以设计dp[u][v][w]表示在这种局面下,还需多少步才能猜出答案

dp转移方程如下:

int DP(int u,int v,int w){
    if(dp[u][v][w] != inf)    return dp[u][v][w];
    for(int i = 2; i <= u + v + w; ++i){
        if(i <= u){
            int x = i;
            cmin(u,v,w,max(DP(u-x+1,v,w),DP(x-1+v,0,0)) + 1,i);
        }
        else if(i <= u + v){
            int x = i - u;
            cmin(u,v,w,max(DP(x-1,v-x+1,w),DP(u,x-1,v-x+1)) + 1,i);
        }
        else{
            int x = i - u - v;
            cmin(u,v,w,max(DP(0,0,w-x+1+v),DP(u,v,x-1)) + 1,i);
        }
    }
    return dp[u][v][w];
}

即枚举小B猜哪里,后面那个max分别对应小A说否,和小A说是,因为小A希望最大化,所以取max

这样的时间复杂度是n ^ 4的,显然不能接受

 

注意到值域很小是log级别的,我们考虑改写DP,设f[u][v][k] 表示最大的w,满足dp[u][v][w] <= k

注意到如果小A回答否,则接下来的转移与w无关,我们考虑先固定u,v,k算出只转移左边,最远能到哪里(相当于算出决策点在哪里)

如果决策点知道了那就很好办了,因为我们只要考虑决策点x在u段内还是v段内,就可以照着上面dp方程直接转移了,即

                if(x <= i)        dp[k][i][j] = max(dp[k][i][j],dp[k-1][i-x+1][j]);/*照着上面的dp方程写*/
                else if(i + j - x + 1 >= 0)    dp[k][i][j] = max(dp[k][i][j],dp[k-1][x-i-1][i+j-x+1]);

此处i,j表示u,v,dp是改写后的,即题解中的f数组

然后问题瓶颈就在于固定u,v如何快速算出决策点x

暴力枚举显然是n^3的,仍然无法通过2000的数据

 

我们继续观察朴素的dp方程

if(i <= u)
   -> DP(x-1+v,0,0)
else if(i <= v)
    -> DP(u,x-1,v-x+1)

我们发现对于一个固定的u和k,v越大,决策点反而越要向左;

为什么呢

  dp(x-1+v,0,0)就很显然了(x + v要一样,v越大,x越小)

至于第二个,感性理解一下,肯定是a[y] = 1的比较少,需要的次数比较少

于是我们得到了一个决策的单调性 p[k][u][v] >= p[k][u][v + 1]

然后就可以O(n ^ 2log)做题了

完整代码如下

/*A.[21省选day1]基础博弈练习题*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read(){
    char c = getchar();
    int x = 0;
    while(c < '0' || c > '9')        c = getchar();
    while(c >= '0' && c <= '9')        x = x * 10 + c - 48,c = getchar();
    return x;
}
const int N = 2010;
int dp[21][N][N],n;
int p[21][N][N];
#define inf 0x3f3f3f3f
/*int DP(int u,int v,int w){
    if(dp[u][v][w] != inf)    return dp[u][v][w];
    for(int i = 2; i <= u + v + w; ++i){
        if(i <= u){
            int x = i;
            cmin(u,v,w,max(DP(u-x+1,v,w),DP(x-1+v,0,0)) + 1,i);
        }
        else if(i <= u + v){
            int x = i - u;
            cmin(u,v,w,max(DP(x-1,v-x+1,w),DP(u,x-1,v-x+1)) + 1,i);
        }
        else{
            int x = i - u - v;
            cmin(u,v,w,max(DP(0,0,w-x+1+v),DP(u,v,x-1)) + 1,i);
        }
    }
    return dp[u][v][w];
}*/
void solve(int x){
    for(int j = 0; j < 17; ++j){
        if(dp[j][x-1][n-x+1] >= 0){
            printf("%d ",j);
            break;
        }
    }
}
bool check(int k,int i,int j,int p){
    if(p <= i)    return dp[k-1][p-1][0] >= j;
    else    return dp[k-1][i][p-i-1] >= j + i - p + 1;
}
int main(){
    n = read();memset(dp,0xcf,sizeof(dp));
    dp[0][0][0] = 1;dp[0][1][0] = dp[0][0][1] = 0;
    for(int k = 1; k < 17; ++k){
        for(int i = 0; i <= n; ++i){
            for(int j = n - i; ~j; --j){/*越往左转移点反而越大,仔细观察dp方程,越往右,1的那段会转移过来,越难满足*/
                dp[k][i][j] = (dp[k-1][i][j] + (1 << (k - 1)) - j);
                p[k][i][j] = ((j == 0)?1:p[k][i][j+1]);
                while(p[k][i][j] < i + j && check(k,i,j,p[k][i][j] + 1))    p[k][i][j]++;
                int x = p[k][i][j];
                if(x <= i)        dp[k][i][j] = max(dp[k][i][j],dp[k-1][i-x+1][j]);/*照着上面的dp方程写*/
                else if(i + j - x + 1 >= 0)    dp[k][i][j] = max(dp[k][i][j],dp[k-1][x-i-1][i+j-x+1]);
            }
        }
    }
    for(int i = 1; i <= n; ++i)        solve(i);
    return 0;
}

 

posted @ 2021-01-09 11:45  y_dove  阅读(240)  评论(4编辑  收藏  举报