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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章