NOIP模擬測試15「建造城市city(插板法)·轟炸·石頭剪刀布」

建造城市

題解

先思考一個簡單問題

10個$toot$ 放進5間房屋,每個房屋至少有1個$toot$,方案數

思考:插板法,$10$個$toot$有$9$個縫隙,$5$間房屋轉化爲$4$個擋板,放在toot縫隙之間得到$C_{9}^{4}$

10個$toot$ 放進$5$間房屋,每個房屋裏可以沒有$toot$,方案數

思考:插板法使用條件必須是每組至少有1個,那麼我們事先在每個房屋中放一個$toot$變成$15$個$toot$放進$5$個房屋,可以插板法,與上一題類似$C_{14}^{4}$

那麼應用到這個題上呢?

這個題$toot$加了一個上界,非常棒對不對,我們可以考慮容斥

怎麼算$\sum\limits_{i=0}^{i<=n}  {-1}^i *C_{n}^{i}*C_{m-i*k-1}^{n-1}$

前面是枚舉的多的城市,後面枚舉的是多的,怎麼理解呢?

首先我們讓$i$個城市提前選出$i*k$ 個 $toot$,假設我們當前是$i==0$,那麼我們選出的包含0個不符合情況的,1個不符合情況的,2個不符合情況的,3個不符合情況的------n個

我們只需要簡單容斥一下就完了

代碼

     #include<bits/stdc++.h>
using namespace std;
#define ll long long
#define A 10101010
#define mod 998244353
ll ni[A],jie[A];
ll n,m,k;
ll meng(ll x,ll k){
    ll ans=1;
    for(;k;k>>=1,x=x*x%mod)
        if(k&1)
            ans=ans*x%mod;
    return ans;
}
ll C(ll n,ll m){
    return jie[n]*ni[n-m]%mod*ni[m]%mod;
}
int main(){
    jie[0]=1;
    for(ll i=1;i<=10000000;i++)
        jie[i]=jie[i-1]*i%mod;
    ni[10000000]=meng(jie[10000000],mod-2);
    for(ll i=9999999;i>=1;i--)
        ni[i]=ni[i+1]*(i+1)%mod;
    ni[0]=1;
/*    while(1){
        ll a,b;
        scanf("%lld%lld",&a,&b);
        cout<<C(a,b)<<endl;
    }
*/    scanf("%lld%lld%lld",&n,&m,&k);
    ll ans=0,tmp=1;
    for(ll i=0;i<=n;i++){
        if(n-1>m-i*k-1) break;
        ans=(ans+tmp*C(n,i)*C(m-i*k-1,n-1)%mod)%mod;
        tmp=-tmp;
    }
    cout<<(ans+mod)%mod<<endl;
}

轟炸

語文不好被坑了

一句話題解

tarjan縮點+最長鏈

代碼不放了

石頭剪刀布

很奇妙的思路,但覺的現在還是似懂非懂的。

首先這個題是先計算所有概率,最後再統計貢獻

和以往做的期望題不太一樣

這個題沒有順序很噁心,思考換一種方法避免無序帶來影響

我們發現如果只開三維,最後根本無法統計答案,統計起來會像一坨

嘗試開四維?

$f[i][j][k][4]$

$f[i][j][k][1]$表示已經有$i$個人出石頭,$j$個人出剪刀,$k$個人出布,下一輪出石頭

$f[i][j][k][2]$表示已經有$i$個人出石頭,$j$個人出剪刀,$k$個人出布,下一輪出剪刀

$f[i][j][k][3]$表示已經有$i$個人出石頭,$j$個人出剪刀,$k$個人出布,下一輪出布

這樣答案轉移起來就很好轉移了

$ans=\sum\limits _{i=1}^{i<=n} max(f[i][j][k][1]+f[i][j][k][2]*3,f[i][j][k][2]+f[i][j][k][3]*3,f[i][j][k][3]+f[i][j][k][1]*3)$

思考狀態轉移

直接轉移肯定難以轉移,開輔助數組$g[i][j][k]$表示出$i$個石頭,$j$個剪刀,$k$個布的概率

顯然我們可以得到

$f[i][j][k][u]=f[i-1][j][k][u]*x[o][1]+f[i][j-1][k][u]*x[o][2]+f[i][j][k-1][u]*x[o][3]+(x[o][1]+x[o][2]+x[o][3])*g[i][j][k]$

思考這是什麼意思

拿1舉例,你當前狀態已經這樣,你又往裏面放了一個人,他出了石頭,那麼當前概率就等於之前的概率*轉移過來的概率

下面是迪哥解釋

        //當u>0時就不太一樣了,計算的是接下來出1的概率
        //它由上一輪對方出1的概率乘對方真的出了1的概率累加而來,此時i+j+k!=s
        //因爲你把這玩意當成一個揹包不斷往裏面放對手來更新其概率
        //意思大概就是“目前的狀態已經是那樣了而且下一輪你遇到了s”,然後s對你的概率產生的貢獻
        //所以就是你走到原狀態的概率,乘上s出1的概率,就是s對目前狀態的概率貢獻
        //所以i+j+k==s時不能枚舉到3,因爲相當於你的原狀態裏面已經有s個人了,可是你現在剛剛開始考慮第s個人啊

細節

你最後統計答案時不能枚舉到n因爲n沒有下一個人了

吳迪帶註釋代碼

#include<bits/stdc++.h>
using namespace std;
//首先題意可能還有人理解錯了。題目的意思是你要根據對手分別出了幾個石頭幾個剪刀來決策
//而並不是一場戰鬥結束後你就能知道對方具體是誰從而直接推斷剩下的人
#define d(x,k) for(int x=k;x>=0;--x)//壓行,字少
int n;double x[51][4],f[51][51][51][4],ans,c[51][51];
//f數組的含義:當最後一維爲1~3時表示第i+j+k+1個人在前面的人出了i個1,j個2,k個3的情況下出1~3的概率
//當最後一維爲0時表示前i+j+k個人出了i個1,j個2,k個3的概率,即那個題解裏的g數組
int main(){ 
    scanf("%d",&n); f[0][0][0][0]=1;//初始化
    for(int i=1;i<=n;++i) scanf("%lf%lf%lf",&x[i][1],&x[i][3],&x[i][2]),
        x[i][1]/=300,x[i][2]/=300,x[i][3]/=300;
    //讀入概率,注意順序是132。把石頭剪刀步分別抽象爲123,故1勝2,2勝3,3勝1
    for(int i=0;i<=50;++i) c[i][0]=1;
    for(int i=1;i<=50;++i) for(int j=1;j<=i;++j) c[i][j]+=c[i-1][j-1]+c[i-1][j];
    //楊輝三角。注意:要用到50!級別的而沒有取模,所以要開long long或double
    for(int s=1;s<=n;++s) d(i,s) d(j,s-i) d(k,s-i-j) d(u,(i+j+k==s?0:3)){//有點像個揹包
        //你可以把s單獨再開一維的數組來表示目前考慮到第幾個人,更好理解但貌似會炸內存
        //u爲1~3時,分別枚舉第幾個人,前面的人出過幾個1,2,3,這個人要出u
        //注意u的枚舉是當i+j+k!=s時才更新對方下一次出123的概率,否則只更新到達某狀態的概率
        //u爲0時,計算到達這個狀態的總概率(即題解中的g數組)
        if(i)f[i][j][k][u]+=f[i-1][j][k][u]*x[s][1];//這個人s出了1,累加概率
        //當u=0時,f[i][j][k][0]由f[i-1][j][k][0]轉移而來(u=0並不考慮下一個人會出什麼)
        //在原狀態出一個1即爲新狀態,後者的概率爲x[s][1]。計算g數組就不必考慮其他f值的影響
        //因爲根據含義就有f[i][j][k][0]=f[i][j][k][1]+f[i][j][k][2]+f[i][j][k][3]
        //當u>0時就不太一樣了,計算的是接下來出1的概率
        //它由上一輪對方出1的概率乘對方真的出了1的概率累加而來,此時i+j+k!=s
        //因爲你把這玩意當成一個揹包不斷往裏面放對手來更新其概率
        //意思大概就是“目前的狀態已經是那樣了而且下一輪你遇到了s”,然後s對你的概率產生的貢獻
        //所以就是你走到原狀態的概率,乘上s出1的概率,就是s對目前狀態的概率貢獻
        //所以i+j+k==s時不能枚舉到3,因爲相當於你的原狀態裏面已經有s個人了,可是你現在剛剛開始考慮第s個人啊
        if(j)f[i][j][k][u]+=f[i][j-1][k][u]*x[s][2];//出2,同上
        if(k)f[i][j][k][u]+=f[i][j][k-1][u]*x[s][3];//出3,同
        if(u)f[i][j][k][u]+=f[i][j][k][0]*x[s][u];
        //這個就是彌補了上面的缺陷。本層轉移。不管目前的狀態是什麼,反正第s個人就是出u了
        //與上面的並不重複。一個是在說s對以前的狀態的貢獻,這個是在說s對當前狀態的貢獻
    }
    d(i,n-1) d(j,n-1-i) d(k,n-i-j-1)//i+j+k不要枚舉到n,因爲已經進行過n輪後下一次再出什麼已經不重要不記分了
        ans+=max(max(f[i][j][k][1]+3*f[i][j][k][2],f[i][j][k][2]+3*f[i][j][k][3]),f[i][j][k][3]+3*f[i][j][k][1])/c[n][i+j+k]/(n-i-j-k);
    //在每一種狀態下(即確定對手已經出了i個1,j個2,k個3)時你都有唯一確定的最優決策來進行下一輪
    //每一次決策時都會累加分數,3種決策分別對應出1,2,3.f[i][j][k][1]即爲與1打平,3*f[i][j][k][2]即爲戰勝2
    //你所說的最優決策就是根據已有信息(每個對手出了什麼),通過猜測對手下一步會出什麼來權衡3中決策
    //至於爲什麼用到了組合數:因爲你所算的概率只是到達這一步的概率,但是你是從n個人裏隨便選出了c[n][i+j+k]個人
    //然而其實在同一場遊戲中對於同樣的i+j+k你只會選1次,在計算的時候你把概率累加在一起了,現在要求一個平均值
    //再除一個(n-i-j-k)的原因也差不多,因爲你是要從剩下的(n-i-j-k)個人裏選出一個去挑戰
    //這一步的概率是1/(n-i-j-k),然而你在上面5層循環的時候並沒有考慮,所以在這裏統一除去
    printf("%.12lf\n",ans);//給的std裏是用%.12f輸出double的,真是驚奇
}//把註釋全刪掉你就會發現這個代碼只有21行811B
View Code

我這個菜雞的代碼

#include<bits/stdc++.h>
using namespace std;
#define A 52
#define ll long long
double f[A][A][A][5],x[A][5];
double ans=0;
ll C[A][A],n;
int main(){
    f[0][0][0][0]=1;
    scanf("%lld",&n);
    for(ll i=0;i<=n;i++)
        C[i][0]=1;
    for(ll i=1;i<=n;i++)
        for(ll j=1;j<=i;j++)
            C[i][j]=C[i-1][j]+C[i-1][j-1];
    for(ll i=1;i<=n;i++)
        scanf("%lf%lf%lf",&x[i][1],&x[i][3],&x[i][2]),
        x[i][1]/=300,x[i][3]/=300,x[i][2]/=300;
    for(ll o=1;o<=n;o++){
        for(ll i=o;i>=0;i--){
            for(ll j=o-i;j>=0;j--){
                for(ll k=o-i-j;k>=0;k--){
                    for(ll u=(((i+j+k)==o)?0:3);u>=0;u--){
                        if(i)
                            f[i][j][k][u]+=f[i-1][j][k][u]*x[o][1];
                        if(j)
                            f[i][j][k][u]+=f[i][j-1][k][u]*x[o][2];
                        if(k)
                            f[i][j][k][u]+=f[i][j][k-1][u]*x[o][3];
                        if(u)
                            f[i][j][k][u]+=f[i][j][k][0]*x[o][u];
                    }
                }
            }
        }
    }
    for(ll i=n-1;i>=0;i--)
        for(ll j=n-1-i;j>=0;j--)
            for(ll k=n-1-i-j;k>=0;k--){
                ans+=max(max(f[i][j][k][1]+f[i][j][k][2]*3,f[i][j][k][2]+f[i][j][k][3]*3),f[i][j][k][3]+f[i][j][k][1]*3)/C[n][i+j+k]/(n-i-k-j);
            }
    printf("%.12lf\n",ans);
}
View Code

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章