1. 程式人生 > 其它 >nowcoder11254A Guess and lies(2021牛客暑期多校訓練營3)dp

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;
}