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\)
轉化後我們可以發現,在排除掉當前狀態中所有的\(\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]\)為答案為\(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;
}