1. 程式人生 > >【CF98E】Help Shrek and Donkey

【CF98E】Help Shrek and Donkey

方便 繼續 註意 ace max 完全 直接 printf 哪些

Portal --> CF98E

Description

  兩個人快樂玩牌(總共\(n+m+1\)張),\(A\)\(n\)張牌,\(B\)\(m\)張牌,還有一張反著放在桌面上,每個人都知道這\(n+m+1\)張牌有哪些,但是並不知道具體是那張,\(A\)只知道自己手頭上的牌具體是哪些,\(B\)也是,這個時候兩個人可以輪流進行操作,每次操作有兩種選擇:

(1)問對方手頭上有沒有某張牌,如果有那麽對方要將這張牌丟掉,如果沒有就說沒有

(2)猜桌面上反著的那張牌是啥,猜錯涼掉,猜對就win了,不管結果如何都是遊戲結束

  問先手和後手的勝率分別是多少(都采取最優決策)

  

Solution

  這題考的是腦子qwq像我這種人要是去玩這個遊戲估計輸都不知道怎麽輸的。。

?  不過感覺整個思考的過程非常有意思qwq好好玩的題qwq

  首先比較顯然的一個事情就是猜牌這種那麽高風險的事情當然是放到最後確定了才去猜,假設當前進行操作的一方為\(A\),另一方為\(B\),我們現在只考慮詢問,那麽有一個很重要的策略就是:\(A\)可以問\(B\)有沒有自己手頭上的牌

  這個“陰險”的操作的目的是讓\(B\)誤以為\(A\)並沒有這張牌,而同時\(B\)也知道自己沒有這張牌,那如果是這樣,\(B\)就會認為桌面上的牌是這張,一旦自認為確定了桌面上的牌是什麽,\(B\)就會去猜,然後\(B\)

會猜錯那麽\(A\)就win了

  那麽現在可以根據這個將\(A\)的詢問分為兩種:真詢問(問的是\(A\)沒有的牌),假詢問(問的是\(A\)有的牌),接下來分兩類討論:

  為了方面下面的描述,我們記\(f(n,m)\)表示先手有\(n\)張牌後手有\(m\)張牌的時候先手的勝率

(1)如果說\(A\)進行的是一次真詢問:

?  首先我們要根據\(A\)問的牌進行分類:

?  1、\(A\)問到的是桌上那張(有\(\frac{1}{m+1}\)的概率)

?  這個時候如果說\(B\)認為\(A\)在做真詢問那麽\(B\)就直接猜中桌上那張是什麽了,\(A\)的勝率為\(0\)

  如果說\(B\)

認為\(A\)在做假詢問那麽\(B\)接下來永遠不會猜這張牌,那必定涼掉,此時\(A\)勝率為 \(1\)

  2、\(A\)問到的是\(B\)手頭上的一張(有\(\frac{m}{m+1}\)的概率)

?  \(B\)會丟掉這張牌,然後變成\(B\)先手,遊戲繼續,此時\(A\)的勝率是\(1-f(m-1,n)\)

  

(2)如果說\(A\)進行的是一次假詢問:

  這裏就可以直接按照\(B\)的判斷分類討論了:

?  1、如果說\(B\)認為\(A\)在做真詢問,那麽\(B\)就會認為桌上那張就是這張,然後涼掉,\(A\)的勝率為\(1\)

  2、如果說\(B\)認為\(A\)在做假詢問,那麽就相當於\(B\)多確定了一張牌,也就是說\(A\)這張牌相當於“廢了”,我們可以直接視為\(A\)將這張牌丟掉然後\(B\)變成先手繼續遊戲,也就是說這個時候\(A\)的勝率是\((1-f(m,n-1))\)

  

?  現在不確定的東西就是,\(A\)做真詢問還是假詢問

?  我們假設\(B\)每次有\(p\)的概率認為\(A\)在做真詢問,因為\(B\)十分厲害(采取最優策略什麽的)所以應該是在“認為是真詢問”和“認為是假詢問”裏面取min,那麽我們可以列出dp式子:
\[ \begin{aligned} B認為真&:f(n,m)=p(\frac{m}{m+1}(1-f(m-1,n))+(1-p)\B認為假&:f(n,m)=p(\frac{m}{m+1}(1-f(m-1,n)+\frac{1}{m+1}))+(1-p)(1-f(m,n-1))\\end{aligned} \]
  然後\(f(n,m)\)的實際值應該就是這兩個裏面取最小了

  那麽現在\(A\)想讓這個\(f(n,m)\)盡量大,而\(A\)能做的就是決定這個\(p\),所以我們現在要解決的問題其實相當於求一個\(p\)使得上面兩個式子的最小值最大,註意到如果將\(p\)看成未知數的話,上面兩個式子可以看成\(p\)的一個一次函數,並且一個遞增一個遞減,那所以直接取交點就好了

?  實現上的話。。為了方便選擇遞歸轉移

  

?  代碼大概長這個樣子

?  (寫的時候放棄思考qwq其實完全可以先手動化簡一波這樣式子會簡潔很多qwq)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1010;
double f[N][N];
int n,m;
void solve(int n,int m){
    if (f[n][m]!=-1) return;
    if (n==0) f[n][m]=1.0/(1.0*m+1.0);
    else if (m==0) f[n][m]=1;
    if (f[n][m]!=-1) return;
    solve(m-1,n);
    solve(m,n-1);
    double k1=f[m][n-1]-(1.0*m)/(1.0*m+1.0)*f[m-1][n],b1=1.0-f[m][n-1];
    double k2=(1.0*m)/(1.0*m+1.0)*(1.0-f[m-1][n])-1.0,b2=1.0;
    double p=(b2-b1)/(k1-k2);
    f[n][m]=k1*p+b1;
    return;
}

int main(){
#ifndef ONLINE_JUDGE
    //freopen("a.in","r",stdin);
#endif
    scanf("%d%d",&n,&m);
    int mx=max(n,m);
    for (int i=0;i<=mx;++i)
        for (int j=0;j<=mx;++j)
            f[i][j]=-1;
    solve(n,m);
    printf("%.9lf %.9lf\n",f[n][m],1.0-f[n][m]);
}

【CF98E】Help Shrek and Donkey