nowcoder11254A Guess and lies(2021牛客暑期多校训练营3)dp
题意
Alice和Bob在玩猜数字游戏。开始,Alice选一个\(1-n\)之间的数\(y\)。Bob每次给Alice一个\(x\),问Alice是否有\(y\ge x\)。Alice有一次欺骗Bob的机会。Alice希望最大化轮数,Bob则希望最小化轮数。问对于\(x_0=1,2,\dots,n\)(即Bob第一次问的值为\(x_0\)且Alice第一次回答为\(yes\)时),期望增加的轮数(第一次不算)是多少?(\(2\le n\le2000\))
分析
首先,Bob有一个比较差的策略是每个点问两次,如果两次不同再问第三次,从而可以二分判断\(y\)的值,那么答案的上界即为\(2\lceil log(n)\rceil+1\)。
然后,我们看Bob的一次询问获得了什么信息。对于一次询问\(x\),如果Alice回答了\(yes\)则对所有真正答案在\([1,x-1]\)的情况撒了一次谎;如果回答了\(no\)则对真正答案在\([x,n]\)的情况撒了一次谎。
所以我们可以记录一个\(a\)数组,\(a_i\)表示如果答案为\(i\)那么Alice撒了几次谎。显然Alice只能撒一次谎,那么\(a\)数组中值大于等于\(2\)的位置一定不是Alice一开始想的\(y\)。那么游戏就变为了Bob选取\([1-n]\)的一个划分,Alice要把其中一部分加一,直到只出现一个位置的\(a_i\le1\),游戏结束,\(y\)即为\(i\)。
转化后我们可以发现,在排除掉当前状态中所有的\(\ge2\)的数后,剩下的一定是\(a\)个\(0\),\(b\)个\(1\),\(c\)个\(0\)的情况(\(a,b,c\ge0\))。初始的情况是Alice回答了\(yes\),然后形成了一个类似\(111000\)的串。然后对于每次询问\(110011\)可以划分在中间形成\(221011\)或\(110122\),或者是划分在两端形成\(210011\)或\(121122\)。其中\(121122\)可以看作\(a=1,b=0,c=2\)的情况,因为中间的\(2\)本质不影响。
对于这个问题,首先可以可以列出一个\(O(n^4)\)的dp:\(dp[a][b][c]\)表示当前状态步数最小值,\(dp[a][b][c]=min\{max(dp[a-i][b][c],dp[i][0][b]),max(dp[i][b-i][c],dp[a][i][b-i]),max(dp[a][b][i],dp[b][0][c-i])\}\),即每次三种切割方式,两种赋值Alice取最大的那种结果。利用决策单调性可以优化到\(O(n^3)\)。
考虑到第一维越长答案也越大,可以把第一维和答案交换。那么可以定义\(dp[a][b][c]\)为答案为\(a\)时\(101\)中有\(b\)个\(0\)和\(c\)个\(1\)时,最长前导\(1\)有多长。然后又可以发现原式中前面的\(1\)和后面的\(1\)是等价的,可以通过枚举\(a,b\)找到一些前导\(1\)和后接\(1\)的pair。因为求的是最长的\(1\),所以大的pair是可以推出小的pair的,之后再求一个后缀最大值即可。
代码
#include <bits/stdc++.h>
using namespace std;
constexpr int inf=1e9;
constexpr int N(2005),A(25);
int x01[A][N][N];
int main() {
for(int i=0;i<A;i++) for(int j=0;j<N;j++) fill(x01[i][j],x01[i][j]+N,-inf);
x01[0][0][0]=1;
x01[0][1][0]=0;
x01[0][0][1]=0;
for(int a=1;a<A;a++) {
for(int m=0;m<N;m++) {
vector<int>mx(N,-inf);
for(int r=0;r<N;r++) { // cut 1
int l=x01[a-1][m][r];
l+=x01[a-1][0][m];
if(l>=0) {
l=min(l,N-1);
mx[l]=max(mx[l],r);
mx[r]=max(mx[r],l);
}
}
for(int m1=0;m1<=m;m1++) { // cut 0
int m2=m-m1;
int l=x01[a-1][m1][m2];
int r=x01[a-1][m2][m1];
if(l>=0 && r>=0) {
l=min(l,N-1);
r=min(r,N-1);
mx[l]=max(mx[l],r);
mx[r]=max(mx[r],l);
}
}
for(int r=N-2;r>=0;r--)
mx[r]=max(mx[r],mx[r+1]); // transform from larger pair
for(int r=0;r<N;r++)
x01[a][m][r]=mx[r];
}
}
int n;
cin>>n;
for(int m=n;m>=1;m--) {
int ans;
for(ans=0;ans<A;ans++) {
if(x01[ans][m][n-m]>=0)
break;
}
cout<<ans<<" \n"[m==1];
}
return 0;
}