[基础博弈论练习题](性质(神仙)题 + 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; }