巴哈姆特

首頁 哈啦區 爐石戰記

【討論】前兩回合時,手上同時抽到兩張龍鰻、一張學徒、和一張鏡像的機率?

追蹤話題

【討論】前兩回合時,手上同時抽到兩張龍鰻、一張學徒、和一張鏡像的機率?

綜合心得
彼時楓憶 (daniel580804) 2018-10-18 05:21:52
#1
之前看到版上有人在聊這個問題,自己剛考完的機率論考試所以忍不住就想要解解看。然而,實際動手算之後發現因為有換牌的關係,比想像中難算很多。

於是乎,這就是學校教的C++能派上用場的時候啦!因為寫程式能力有點生疏,所以花了一整個晚上才寫好。

但是!!!非常令人驚訝,機率竟然不到1%

以下是執行結果:
舉例來說:
0 1 1 0
0.015873  1201

是指:“沒換牌前,龍鰻0張學徒至少1鏡像至少1張”的情況— 在總共的27405個組合中,一共出現了1201次。
而在這種特定的情況下,前兩回合就有抽齊兩張龍鰻、一張學徒、和一張鏡像的條件機率是0.015873


那麼,就只好把程式碼貼上來,和大家討論實際的機率啦~
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <array>
#include <vector>
using namespace std;

//returns all (-1) if having no other ways to choose
void selection_changeToNextState(int numOfThingsToChooseFrom, int numOfThingsChosen, int nowState[]);

int main(int argc, const char * argv[]) {
    const int MaxCards = 30;
    int rounds = 2;
    
    array<int,MaxCards> cards={};
    cards[0]=2;
    cards[1]=2;
    cards[2]=2;
    cards[3]=24;
    
    
    array<int,MaxCards> goal={};
    goal[0]=2;
    goal[1]=1;
    goal[2]=1;
    goal[3]=0;
    
    array<int,MaxCards> deck={};
    int nowCard=0;
    int importantTypes=0;
    for(int nowType=0; nowType<MaxCards; ++nowType){
        if(cards[nowType]>0){
            importantTypes = nowType+1;
            
            for(int j=nowCard; j< nowCard+cards[nowType]; ++j){
                deck[j]=nowType;
            }
            
            nowCard+=cards[nowType];
        }
    }
    int totalCards = nowCard;
    
    cout<<"totalCards:\t"<<totalCards<<"\n";
    cout<<"importantTypes:\t"<<importantTypes<<"\n";
    
     //for testing: show the entire deck
     /*for(int i=0;i<totalCards;++i){
        cout<<i<<":\t"<<deck[i]<<"\n";
    }*/
    
    bool isFirst = false;
    
    int firstDrawNum;
    
    if(isFirst)
        firstDrawNum=3;
    else
        firstDrawNum=4;
    
    
    //hand starts from "choosing the first <firstDrawNum> number of cards"
    array<int,MaxCards> hand = {};
    for(int i=0;i<firstDrawNum;++i){
        hand[i]=i;
    }
    
    bool testShow_cardTypeDrawn = false;
    bool testShow_possible_hands = false;
    bool testShow_possible_afterHands = false;
    bool testShow_unfulfilledGoal = false;
    bool testShow_finalGoal = false;
    bool testShow_cardsLaterDrawn = false;
    bool testShow_count = true;
    bool testShow_finishProbability = false;
    
    int count=1;
    
    struct cardTypesKept_to_probability{
        array<int,MaxCards> cardTypesKept;
        double probability;
        int count;
    };
    
    vector<cardTypesKept_to_probability> record;
    
    while(true){
        int cardsKept = 0;
        int cardsToRedraw = 0;
        array<int,MaxCards> unfulfilledGoal = goal;
        array<int,MaxCards> cardTypesKept = {};
        array<bool,MaxCards> cardIsKept = {};
        
        for(int j=0;j<firstDrawNum;++j){
            int cardDrawn = deck[hand[j]];
            
            // for testing: show card type drawn
            if(testShow_cardTypeDrawn)
                cout<<cardDrawn<<"\n";
            
            if(unfulfilledGoal[ cardDrawn ]>0){
                unfulfilledGoal[ cardDrawn ]--;
                cardTypesKept[ cardDrawn ]++;
                cardIsKept[ hand[j] ] = true;
                ++cardsKept;
            }
            else{
                ++cardsToRedraw;
            }
        }
        
        
        // for testing: show card type drawn
        if(testShow_cardTypeDrawn){
            cout<<"\n";
            for(int j=0;j<importantTypes;++j){
                cout<<cardTypesKept[j]<<"\t";
            }
            cout<<"\n";
        }
        
        bool inRecord = false;
        
        for(int j=0;j<record.size();++j){
            if(cardTypesKept == record[j].cardTypesKept){
                ++record[j].count;
                inRecord = true;
            }
        }
        if(!inRecord){
            int cardsLaterDrawn = cardsToRedraw + (rounds-1) +1;
            
            array<int,MaxCards> calcHandsAfter = {};
            for(int i=0;i<cardsLaterDrawn;++i){
                calcHandsAfter[i]=i;
            }
            
            int afterCount = 1;
            int finishCount = 0;
            while(true){
                // for testing: show all the possible hands after drawing again
                if(testShow_possible_afterHands){
                    cout<<"\t\t";
                    cout<<afterCount<<"~  ";
                    for(int j=0;j<cardsLaterDrawn;++j){
                        cout<<calcHandsAfter[j]<<" ";
                    }
                    cout<<"\n";
                }
                
                array<int,MaxCards> finalGoal = unfulfilledGoal;
                bool goalFinished = true;
                
                int nowPlace=0;
                int nowAfterCode=0;
                for(int stepsMoved=0; nowPlace<totalCards;++nowPlace){
                    if(cardIsKept[nowPlace]){
                        if(testShow_possible_afterHands)
                            cout<<nowPlace<<" ";
                    }
                    else{
                        if(stepsMoved == calcHandsAfter[nowAfterCode]){
                            if(testShow_possible_afterHands)
                                cout<<nowPlace<<" ";
                            
                            int cardDrawnAfter = deck[nowPlace];
                            if(finalGoal[ cardDrawnAfter ]>0){
                                finalGoal[ cardDrawnAfter ]--;
                            }
                            
                            ++nowAfterCode;
                        }
                        ++stepsMoved;
                    }
                }
                if(testShow_possible_afterHands)
                    cout<<"\n";
                
                for(int j=0;j<importantTypes;++j){
                    if(finalGoal[j]>0){
                        goalFinished=false;
                    }
                }
                if(goalFinished)
                    ++finishCount;
                
                // for testing: show finalGoal
                if(testShow_finalGoal){
                    for(int j=0;j<importantTypes;++j){
                        cout<<finalGoal[j]<<" ";
                    }
                    cout<<"\n";
                }
                
                selection_changeToNextState(totalCards-cardsKept, cardsLaterDrawn, calcHandsAfter.data());
                
                //if having no other ways to choose
                if(calcHandsAfter[0]<0)
                    break;
                
                ++afterCount;
            }
            
            double finishProbability = (finishCount * 1.0)/afterCount;
            struct cardTypesKept_to_probability c_p={cardTypesKept,finishProbability,1};
            record.push_back(c_p);
            //cout<<finishProbability<<"\n";
        }
        
        // for testing: show all the possible hands
        if(testShow_possible_hands){
            cout<<count<<":  ";
            for(int j=0;j<firstDrawNum;++j){
                cout<<hand[j]<<" ";
            }
            cout<<"\n";
        }
        
        // for testing: show unfulfilledGoal
        if(testShow_unfulfilledGoal){
            cout<<count<<"–  ";
            for(int j=0;j<importantTypes;++j){
                cout<<unfulfilledGoal[j]<<" ";
            }
        }
        
        
        selection_changeToNextState(totalCards, firstDrawNum, hand.data());
        
        //if having no other ways to choose
        if(hand[0]<0)
            break;
        
        ++count;
    }
    
    //for testing
    if(testShow_count)
        cout<<count<<"\n";
    
    
    double sumOfProbability = 0.0;
    
    for(int i=0;i<record.size();++i){
        sumOfProbability += (record[i].probability)*(record[i].count);
        for(int j=0;j<importantTypes;++j){
            cout<<record[i].cardTypesKept[j]<<"\t";
        }
        cout<<"\n";
        cout<<record[i].probability<<"\t"<< record[i].count <<"\n";
        cout<<"\n";
    }
    
    cout<<"\n"<< sumOfProbability/count <<"\n";
    return 0;
}


//returns all (-1) if having no other ways to choose
void selection_changeToNextState(int numOfThingsToChooseFrom, int numOfThingsChosen, int nowState[]){
    int lastDigit = numOfThingsChosen-1;
    int lastNum = numOfThingsToChooseFrom-1;
    int carryDigits = 0;
    while(nowState[lastDigit - carryDigits] >= lastNum-carryDigits){
        ++carryDigits;
        if(lastDigit - carryDigits<0)
            break;
    }
    //return all (-1) if having no other ways to choose
    if(lastDigit - carryDigits<0){
        for(int j= 0; j<=lastDigit; ++j){
            nowState[j]= -1;
        }
        return;
    }
    
    nowState[lastDigit - carryDigits]++;
    for(int j= lastDigit - carryDigits+1; j<=lastDigit; ++j){
        nowState[j]=nowState[j-1]+1;
    }
    
}

跑出來的結果比我預期中小很多。大家覺得,這一種情況的機率是多少呢?

看較舊的 24 則留言

透明エレジー: B6 2018-10-18 08:09

在我對面天胡的機率高達90%

阿平a好麻吉: B26 2018-10-22 08:10

偷偷告訴你,遊戲類的運算程式碼跟你寫的不同,用HDT紀錄一下就懂了

可撥韭菜: B27 2018-10-23 08:51

我是覺得 1-2費的某幾張職業卡 機率有刻意調高 感覺太明顯了

可撥韭菜: B28 2018-10-23 08:53

其他職業也有 某幾張1-2費特別容易抽到的問題

憤怒熊熊: B29 2018-10-25 01:21

這就是莫非定律厲害的地方

彼時楓憶 (daniel580804) 2018-10-18 08:40:27
#2
補充,因為只有程式碼的話太抽象,所以附上用手算的大概過程

後手 ,1費有龍鰻—

換牌前沒有的機率:
p = C(28,4)/C(30,4)=
(28*27*26*25)/(30*29*28*27)=
(26*25)/(30*29)=0.747
全換後也沒有的機率:
C(28,4)/C(30,4) = 0.747
後手多抽的那張也沒有的機率:
(30-4-2)/(30-4)=24/26=0.923

第一回合沒有龍鰻的總機率:以上三個相乘 = 0.747*0.747*0.923 = 0.515
第一回合有龍鰻的總機率:
1 - 0.515 = 0.485

看較舊的 1 則留言

彼時楓憶: B2 2018-10-18 08:42

程式做的事類似這樣;只是不是記算C(28,4)/C(30,4),而是列出全部C(28,4)種方式、全部C(30,4)種方式,再把兩者的總和相除。

小煜: B3 2018-10-18 09:55 編輯

手牌全換後不是會排除那四張牌 那不是C(24,4)?

彼時楓憶: B4 2018-10-18 10:12

[ken811325:小煜]換掉的牌,似乎還是會被重新抽起來?不過我也不是完全確定就是啦~

阿德: B5 2018-10-18 10:22

起手換掉的牌 起手那三或四張不會在抽到換掉的牌

阿德: B6 2018-10-18 10:27 編輯

所以其實你起手2費前要全力(全換)抓到成長(以前別人是算2費無恥小斧機率) 先手45%後手55% 但我體感玩德魯依根本沒到這機率 很想找人實測看看機率

彼時楓憶 (daniel580804) 2018-10-18 09:26:18
#3
另外,由程式得出,前兩回合同時有龍鰻+學徒的機率是0.245,約為1/4。
totalCards:30
importantTypes:3
27405
110
11405

100
0.3201975525

010
0.3201975525

000
0.11877414950


0.245169
Program ended with exit code: 0

冰火人 (game2520741) 2018-10-18 10:04:50
#4
我先貼後手起手換牌(共八張)之後有龍鰻加學徒的機率


S=龍鰻
L=學徒

T2有法術的話問題變成10回合內抽出SSLF,F是法術

看較舊的 32 則留言

冰火人: B34 2018-10-18 12:25

[karta1762606:騎單車兜風]我想到讓你理解的方式了。不換牌相當於一開始就中,換一張相當於多抽一張才中,換四張相當於抽八張才中。所以抽四張到抽八張的所有可能都考慮啦

冰火人: B35 2018-10-18 13:44

[karta1762606:騎單車兜風]ok我找到反例了,問題完全不在分母,而是兩種算法不同

冰火人: B36 2018-10-18 13:45

先講結論,用抽八張的方法算這個問題會高估機率,但簡化很多。不同的點在於如果起手都不換,只會是抽六張

冰火人: B37 2018-10-18 13:45

沒辦法變成抽十張的情況去算,因為後面那幾張有沒有中都不知道

Mo: B38 2018-10-18 14:28

換牌階段不會重複抽到換掉的牌,但能在T1開始後抽的第一張牌抽到,HearthstoneWiki那可找到BB當時推特關於mulligan的內容

後來的我 (h0911491849) 2018-10-18 12:46:24
#5
可以幫我算一下術T1小鬼 硬幣 巫醫雙食屍鬼 的機率嗎XD

回到主題
首先是 為什麼要算這個機率呀?

就算讓你2張龍鰻+學徒+鏡像出了
那也只是你在前兩回合就把所有手牌都打玩了
也就是我有東西清場 你就什麼都沒了

遇到快攻還怕你鋪場比他快嗎~
對吧!?


如果只是無聊想算機率的話..
幫我算一下我出生的機率吧XD
2億的競爭 還不能有缺陷 不然去醫院照胎看到缺手缺腳
父母又要是正常夫妻或情侶想生小孩
懷胎10個月還不能有意外

這樣看起來 我應該是人生勝利組 對吧XD


看較舊的 1 則留言

fujacorn: B2 2018-10-18 13:27

把應打成因算不算一種缺陷

後來的我: B3 2018-10-18 15:35

但我還是出生了呀XD 好啦 我改

番茄炒荷包蛋: B4 2018-10-23 10:00

呵呵~快攻神起手也能被你說成乾手牌怕aoe的缺點

番茄炒荷包蛋: B6 2018-10-23 10:12

祝你玩快攻起手沒123費,滿滿手牌不怕對面的aoe

Leonnail (cat521219) 2018-10-23 07:35:21
#6
我怎麼算都是50%呀
有跟沒有阿
不然對面怎麼可能動不動就天胡==
ヽ(́◕◞౪◟◕‵)ノ: B1 2018-10-23 10:15 編輯

那是因為你只看結果 當然是50% XD

貓奈里(,,・ω・,,): B2 2018-10-23 11:31

[tommy014012:ヽ(́◕◞౪◟◕‵)ノ] [tommy014012:ヽ(́◕◞౪◟◕‵)ノ]體感 別人都是神起手100% 我都是高費0%

浮土: B3 2018-10-24 16:00

[Naile44:小奈里(,,・ω・,,)]平均起來剛好50

Leonnail: B4 2018-10-26 21:36

起手雙龍鰻合情合理

延伸閱讀

討論試算卡拉贊改版五張傳送門之實質效益(8/15更新薩2費法術大漩渦傳送門)

2016-08-15 21:45:50

問題請問有中了冰凍陷阱增加消耗(2),下一回合水晶多過消耗仍打不出同一張牌的嗎?

2020-09-11 03:29:14

情報英雄戰場將迎來一波大改動

2024-04-24 03:41:25

情報29.2.2 更新檔說明 (記得你有2個禮拜

2024-04-26 01:49:05

討論大亂鬥裡有一張牌叫做[永遠的好朋友]

2019-10-21 14:49:36

情報29.2.2 構築模式平衡更新預覽

2024-04-24 22:05:40

問題剛回鍋,想玩盜賊但不知道要合那張傳說

2019-05-07 17:00:53

心得英雄戰場現況

2024-04-22 18:16:13

Hololive-星街すいせい

看更多

【討論】活動「ジュビリー」以及星街彗星合作抽卡開催中(~4/2、~4/10)

偶像大師灰姑娘女孩 星光舞台

【心得】如彗星般出現星之原石 星街彗星 GK 塗裝分享

綜合公仔玩具討論區

【翻譯】為了實現星街彗星「THE FIRST TAKE」的演出,在背後支持的團隊們

虛擬 Youtuber(Vtuber)

電梯
開啟 APP

face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】