BZOJ 2963 麻將 DFS+動態規劃

題目大意:給定一副不全的麻將,從中摸取14 張,求和牌的概率,牌型包括一般牌型/七対子/國士無雙

這傻逼題我從昨天中午寫到現在……

答案是分數形式,我們求出和牌的方案數和總方案數C14n ,約分一下即可
比較好的一件事情就是C14n4.251018 ,剛好不爆long long

由於和牌牌型分爲三類,所以一般的思路是分別計算三類牌型的概率,然後加起來,但是這樣不對,因爲一般牌型和七対子是有交集的,叫做二盃口……

一盃口:1番門前役,牌型中存在兩幅同色同數的順子,稱作一盃口
例:1m 1m 2m 2m 3m 3m 5m 5m 5m 7m 7m 7m 9m 9m
牌型中的1m 1m 2m 2m 3m 3m即爲一盃口

二盃口:3番門前役,牌型中存在兩幅一盃口,稱作二盃口
例:1m 1m 2m 2m 3m 3m 5m 5m 6m 6m 7m 7m 9m 9m

容易發現如果二盃口中沒有重疊(即不存在某張牌出現了四張,像1m 1m 2m 2m 3m 3m 3m 3m 4m 4m 5m 5m 7m 7m這樣的形式)那麼二盃口既滿足七対子的形式又滿足一般牌型的形式,要從答案中減去

故答案=國士無雙+七対子+一般牌型-不重疊的二盃口

國士無雙:DP
七対子:DP
二盃口:DP/爆枚,如果用DP的話注意同種牌型的不同解釋方式,例如1m 1m 2m 2m 3m 3m 4m 4m 5m 5m 6m 6m 7m 7m有三種解釋方式,但是隻應被統計一次

然後就是這該死的一般牌型了……

容易想到樸素的DP:fi,j,k,cnt1,cnt2 表示當前在第i 張牌,湊成了j 組面子,k=0/1 表示有/無將牌,此時第i 張牌還剩cnt1 張未匹配,第i1 張牌還剩cnt2 張未匹配的方案數

但是這樣DP出來的結果完全不對,因爲同一牌型可能有不同解釋方式,像3m 3m 3m 4m 4m 4m 5m 5m 5m 6m 6m 8m 8m 8m可以解釋成(3m 3m 3m)(4m 4m 4m)(5m 5m 5m)(8m 8m 8m)(6m 6m)或(3m 4m 5m)(4m 5m 6m)(4m 5m 6m)(8m 8m 8m)(3m 3m),並且一種牌型的解釋方式可能紛繁複雜,我們沒辦法像七対子 一般牌型= 不重疊的二盃口那樣容斥

然後我考慮了下搜索,搜索的好處就是可以哈希判重,很好地迴避了這個問題,而且如果只搜面子和將牌的話搜索量不會太大
然後我測了下,一般牌型的牌型數量是1000W左右,加上搜索以及哈希表的一大坨常數,MLE+TLE到死,極限數據單組4s,玩個卵

然後我想了下meet-in-the-meedle,發現還是沒有什麼好辦法迴避多種解釋方式的問題

DP時間複雜度優秀,但是有BUG;搜索可以避免這個BUG,但是T到死,那麼我們考慮結合一下這倆算法

我們把連在一起的牌稱作一個連通塊,例如1m 1m 2m 2m 3m 3m 5m 5m 6m 6m 7m 7m 8m 8m這組牌就可以分成{1m 1m 2m 2m 3m 3m}{5m 5m 6m 6m 7m 7m 8m 8m}兩個連通塊

容易發現同種牌型不同解釋方式的問題只會在連通塊內部出現,那麼我們可以搜索連通塊,然後用DP把連通塊組合起來

考慮到連通塊的長度最大爲9 ,因此搜索量不會超過49=262144

然後就好辦了
連通塊中一張牌可能出現1,2,3,4 次,我們用一個四進制數表示
搜索得到cnti,j,k,sta 表示長度爲k ,狀態爲sta 的連通塊,其中包含i 組面子和j 組將牌的方案數
然後對cnti,j,k 跑一個子集和變換得到gi,j,k,sta 表示從k 張連續的,狀態爲sta 的牌中選出一個長度爲k ,包含i 組面子和j 組將牌的連通塊的方案數

然後……然後直接DP就行了

時間複雜度很玄學但是跑的很快- -

/*  -
    -
    1m
    2m
    3m
    4m
    5m
    6m
    7m
    8m
    9m
    -
    1s
    2s
    3s
    4s
    5s
    6s
    7s
    8s
    9s
    -
    1p
    2p
    3p
    4p
    5p
    6p
    7p
    8p
    9p
    -
    1c
    -
    2c
    -
    3c
    -
    4c
    -
    5c
    -
    6c
    -
    7c
*/


#include <map>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

int cnt[50],used[50];
long long C[140][20];
map<string,int> pos;
long long g[5][2][10][1<<18];
bool flag[10][1<<18];

void DFS(int i,int j,int k,int max_val,int hash)
{
    int temp=((1<<2*max_val)-1)/3;
    if(max_val && !flag[max_val][hash-temp])
        flag[max_val][hash-temp]=true,g[j][k][max_val][hash-temp]++;
    if(i>9) return ;

    int l;

    //啥也不湊
    if(used[i]) DFS(i+1,j,k,max_val,hash);

    //只湊順子
    if(i<=7)
        for(l=1;l<=4-used[i] && l<=4-j;l++)
        {
            used[i]+=l;used[i+1]+=l;used[i+2]+=l;
            DFS(i+1,j+l,k,i+2,hash+(l<<(i-1)*2)+(l<<(i)*2)+(l<<(i+1)*2));
            used[i]-=l;used[i+1]-=l;used[i+2]-=l;
        }

    //將牌+順子
    if(!k)
    {
        if(used[i]<=4-2)
        {
            used[i]+=2;
            DFS(i+1,j,k+1,max(max_val,i),hash+(2<<(i-1)*2));
            used[i]-=2;
        }
        if(i<=7)
            for(l=1;l<=4-2-used[i] && l<=4-j;l++)
            {
                used[i]+=l+2;used[i+1]+=l;used[i+2]+=l;
                DFS(i+1,j+l,k+1,i+2,hash+(l+2<<(i-1)*2)+(l<<(i)*2)+(l<<(i+1)*2));
                used[i]-=l+2;used[i+1]-=l;used[i+2]-=l;
            }
    }

    //刻子+順子
    if(j!=4)
    {
        if(used[i]<=4-3)
        {
            used[i]+=3;
            DFS(i+1,j+1,k,max(max_val,i),hash+(3<<(i-1)*2));
            used[i]-=3;
        }
        if(i<=7)
            for(l=1;l<=4-3-used[i] && l<=4-1-j;l++)
            {
                used[i]+=l+3;used[i+1]+=l;used[i+2]+=l;
                DFS(i+1,j+l+1,k,i+2,hash+(l+3<<(i-1)*2)+(l<<(i)*2)+(l<<(i+1)*2));
                used[i]-=l+3;used[i+1]-=l;used[i+2]-=l;
            }
    }
}

void Pretreatment()
{
    int i,j,k,l1,l2,l3;
    C[0][0]=1;
    for(i=1;i<=136;i++)
    {
        C[i][0]=1;
        for(j=1;j<=14;j++)
            C[i][j]=C[i-1][j]+C[i-1][j-1];
    }

    pos["1m"]=3;
    pos["2m"]=4;
    pos["3m"]=5;
    pos["4m"]=6;
    pos["5m"]=7;
    pos["6m"]=8;
    pos["7m"]=9;
    pos["8m"]=10;
    pos["9m"]=11;

    pos["1s"]=13;
    pos["2s"]=14;
    pos["3s"]=15;
    pos["4s"]=16;
    pos["5s"]=17;
    pos["6s"]=18;
    pos["7s"]=19;
    pos["8s"]=20;
    pos["9s"]=21;

    pos["1p"]=23;
    pos["2p"]=24;
    pos["3p"]=25;
    pos["4p"]=26;
    pos["5p"]=27;
    pos["6p"]=28;
    pos["7p"]=29;
    pos["8p"]=30;
    pos["9p"]=31;

    pos["1c"]=33;
    pos["2c"]=35;
    pos["3c"]=37;
    pos["4c"]=39;
    pos["5c"]=41;
    pos["6c"]=43;
    pos["7c"]=45;

    DFS(1,0,0,0,0);

    for(i=0;i<=4;i++)
        for(j=0;j<=1;j++)
            for(k=1;k<=9&&k<=i*3+j;k++)
                for(l1=0;l1<k*2;l1+=2)
                    for(l2=(1<<k*2)-1;~l2;l2--)
                    {
                        int temp=(l2>>l1)&3;
                        for(l3=0;l3<temp;l3++)
                            g[i][j][k][l2]+=g[i][j][k][l2^(temp<<l1)^(l3<<l1)]*C[temp+1][l3+1];
                    }
}

long long Calculate1()//國士無雙
{
    static const char s[][10]={"","1m","9m","1s","9s","1p","9p","1c","2c","3c","4c","5c","6c","7c"};
    static long long f[14][2];//f[i][j]表示當前選到第i張牌,有/無對子的方案數
    int i;
    memset(f,0,sizeof f);f[0][0]=1;
    for(i=1;i<=13;i++)
    {
        f[i][0]=f[i-1][0]*C[cnt[pos[s[i]]]][1];
        f[i][1]=f[i-1][0]*C[cnt[pos[s[i]]]][2]+f[i-1][1]*C[cnt[pos[s[i]]]][1];
    }
    return f[13][1];
}

long long Calculate2()//七対子
{
    static long long f[46][8];//f[i][j]表示當前在第i張牌,湊成了j對的方案數
    int i,j;
    memset(f,0,sizeof f);f[0][0]=1;
    for(i=1;i<=45;i++)
    {
        f[i][0]=f[i-1][0];
        for(j=1;j<=7;j++)
            f[i][j]=f[i-1][j]+f[i-1][j-1]*C[cnt[i]][2];
    }
    return f[45][7];
}

long long Calculate3()//一般牌型
{
    static long long f[46][5][2];
    //f[i][j][k]表示當前在第i張牌,湊成了j組面子,有/無將牌的方案數
    int i,j,k,l1,l2,l3;
    memset(f,0,sizeof f);f[0][0][0]=1;
    for(i=1;i<=45;i++)
        for(j=0;j<=4;j++)
            for(k=0;k<=1;k++)
            {
                f[i][j][k]=f[i-1][j][k];
                int temp=0;
                for(l1=1;l1<=9 && cnt[i-l1+1];l1++)
                {
                    temp^=cnt[i-l1+1]-1<<(l1-1)*2;
                    for(l2=0;l2<=j;l2++)
                        for(l3=0;l3<=k;l3++)
                            f[i][j][k]+=f[i-l1-1][l2][l3]*g[j-l2][k-l3][l1][temp];
                }
            }
    return f[45][4][1];
}

long long Calculate4()//無重張的二盃口
{
    static long long f[46][3][2];
    //f[i][j][k]表示當前在第i張牌,湊出了j副一杯口,有/無將牌的方案數
    int i,j,k,l;
    memset(f,0,sizeof f);f[0][0][0]=1;
    for(i=1;i<=45;i++)
        for(j=0;j<=2;j++)
            for(k=0;k<=1;k++)
            {
                f[i][j][k]=f[i-1][j][k];
                long long temp=1;
                for(l=1;l<=7;l++)
                {
                    temp*=C[cnt[i-l+1]][2];
                    if(!temp) break;
                    switch(l)
                    {
                        case 1:if(k)f[i][j][k]+=f[i-l-1][j][k-1]*temp;break;
                        case 3:if(j)f[i][j][k]+=f[i-l-1][j-1][k]*temp;break;
                        case 4:if(j&&k)f[i][j][k]+=f[i-l-1][j-1][k-1]*temp;break;
                        case 6:if(j>=2)f[i][j][k]+=f[i-l-1][j-2][k]*temp;break;
                        case 7:if(j>=2&&k)f[i][j][k]+=f[i-l-1][j-2][k-1]*temp;break;
                    }
                }
            }
    return f[45][2][1];
}

int main()
{
    int T,n,i;
    char p[10];
    Pretreatment();

    for(cin>>T;T;T--)
    {
        memset(cnt,0,sizeof cnt);
        scanf("%d",&n);
        for(i=1;i<=n;i++)
            scanf("%s",p),cnt[pos[p]]++;
        long long a=Calculate1()+Calculate2()+Calculate3()-Calculate4();
        long long b=C[n][14];
        long long gcd=__gcd(a,b);
        a/=gcd;b/=gcd;
        printf("%lld/%lld\n",a,b);
        //cout<<_<<endl;
    }

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