ZJOI2019 麻将

一道清真好题,只是之前没有接触过,才感觉非常困难。
感谢yn大佬。

我的做法是dp套dp的做法。
首先如果你有一个麻将自动机,
这个自动机有若干节点,以及节点之间的转移边,以及一个起始状态节点,和一个终止状态集合。

终止状态集合中的节点代表胡了。
你可以给这个自动机新添加一种花色,麻将的数量在0~4之间,然后沿着转移边走到下一个状态。

如果你有这样一个自动机,那么考虑一个dp,\(f[i][j][k]\)表示考虑了前\(i\)种花色,选了\(j\)张麻将,在自动机的\(k\)节点时的无序集合数。
然后一个一定长度的无序集合,映射到相同个数的排列数。
就可以愉快的算出答案了。

当然期望按照套路转化成还未结束的概率和。

丑陋的代码

#include <bits/stdc++.h>

using namespace std;

typedef long long i64;
const int N=105;
const int MM=4000;
const int M=998244353;

namespace{
    void Addt(int &x,int y){
        (x+=y)>=M?x-=M:0;
    }
    int mul(int x,int y){
        return (i64)x*y%M;
    }
    int fp(int x,int y){
        int ret=1;
        for (; y; y>>=1,x=mul(x,x))
            if (y&1) ret=mul(ret,x);
        return ret;
    }
}
struct st{
    int a[3][3];
    st(){
    }
    st(int x){
        for (int i=0; i<3; ++i)
            for (int j=0; j<3; ++j)
                a[i][j]=x;
    }
    st operator +(const st &_) const{
        st ret;
        for (int i=0; i<3; ++i)
            for (int j=0; j<3; ++j)
                ret.a[i][j]=max(a[i][j],_.a[i][j]);
        return ret;
    }
    st operator +(const int &num) const{
        st ret(-1);
        for (int i=0; i<3; ++i)
            for (int j=0; j<3; ++j)
                for (int k=0; k<3&&i+j+k<=num; ++k){
                    if (a[i][j]==-1) continue;
                    ret.a[j][k]=min(max(ret.a[j][k],a[i][j]+(num-k-i-j)/3+i),4);
                }
        return ret;
    }
    bool operator <(const st &_) const{
        for (int i=0; i<3; ++i)
            for (int j=0; j<3; ++j)
                if (a[i][j]!=_.a[i][j]) return a[i][j]<_.a[i][j];
        return 0;
    }
    bool operator !=(const st &_) const{
        for (int i=0; i<3; ++i)
            for (int j=0; j<3; ++j)
                if (a[i][j]!=_.a[i][j]) return 1;
        return 0;
    }
};
map<st,int> mst;
struct mj{
    st f,g;
    int cnt;//number of different dui zi
    bool operator <(const mj &_) const{
        if (cnt!=_.cnt) return cnt<_.cnt;
        if (f!=_.f) return f<_.f;
        return g<_.g;
    }
    mj operator +(const int &a) const{
        mj ret=*this;
        if (a>=2){
            ret.cnt=min(ret.cnt+1,7);
            ret.g=(ret.g+a)+(ret.f+(a-2));
        }
        else ret.g=ret.g+a;
        ret.f=ret.f+a;
        return ret;
    }
    bool ri(){
        if (cnt>=7) return 1;
        //care
        return g.a[0][0]>=4;
    }
}m_j[MM];
map<mj,int> mmj;
int tots,totm;
void Dfscomplicated(const st &&s){
    if (mst.count(s)) return;
    mst[s]=++tots;
    for (int i=0; i<=4; ++i){
        Dfscomplicated(s+i);
    }
}
void Dfshdaewr(const mj &&m){
    //getchar();
    if (mmj.count(m)) return;
    //cerr<<"Dfshdaewr"<<" "<<totm<<" "<<m.cnt<<" "<<mst[m.f]<<" "<<mst[m.g]<<" "<<(mmj.find(m)==mmj.end())<<endl;
    mmj[m]=++totm;
    //cerr<<mmj[m]<<endl;
    m_j[totm]=m;
    for (int i=0; i<=4; ++i){
        Dfshdaewr(m+i);
    }
}
st zst(){
    st ret=st(-1);
    ret.a[0][0]=0;
    return ret;
}
mj zmj(){
    mj ret;
    ret.f=zst();
    ret.g=st(-1);
    ret.cnt=0;
    return ret;
}
int binomial[5][5],trans[MM][5];
void Prework(){
    for (int i=0; i<=4; ++i){
        binomial[i][0]=1;
        for (int j=1; j<=i; ++j)
            Addt(binomial[i][j]=binomial[i-1][j-1],binomial[i-1][j]);
    }
    //cerr<<"???"<<endl;
    Dfscomplicated(zst());
    //cerr<<"????"<<tots<<endl;
    Dfshdaewr(zmj());
    //cerr<<"?????"<<totm<<endl;
    for (int i=1; i<=totm; ++i)
        for (int j=0; j<=4; ++j){
            trans[i][j]=mmj[m_j[i]+j];
            //cerr<<i<<" "<<j<<" "<<trans[i][j]<<endl;
        }
    //cerr<<"Preworkend"<<endl;
}
int n,usd[N],f[N][N*4][MM];
int main(){
    cin>>n;
    for (int i=1; i<=13; ++i){
        int x,y;
        cin>>x>>y;
        ++usd[x];
    }
    Prework();
    //cerr<<"!!!!!"<<binomial[0][0]<<endl;
    f[0][0][1]=1;
    for (int i=0; i<n; ++i)
        for (int j=0; j<=i*4; ++j)
            for (int d=1; d<=totm; ++d)
                if (f[i][j][d]){
                    //cerr<<i<<" "<<j<<" "<<d<<" "<<f[i][j][d]<<" "<<m_j[d].ri()<<endl;
                    //getchar();
                    int tmp=f[i][j][d];
                    for (int k=usd[i+1]; k<=4; ++k){
                        //cerr<<"trans:"<<k<<" "<<trans[d][k]<<endl;
                        Addt(f[i+1][j+k][trans[d][k]],mul(tmp,binomial[4-usd[i+1]][k-usd[i+1]]));
                    }
                }
    //cerr<<"????"<<endl;
    int ans=0;
    for (int j=13; j<=n*4; ++j){
        int sum=0;
        int nend=0;
        for (int i=1; i<=totm; ++i){
            Addt(sum,f[n][j][i]);
            //if (f[n][j][i]) cerr<<j<<" "<<i<<" "<<f[n][j][i]<<" "<<ans<<" "<<sum<<" "<<nend<<endl;
            if (!m_j[i].ri()) Addt(nend,f[n][j][i]);
        }
        Addt(ans,mul(nend,fp(sum,M-2)));
    }
    cout<<ans;
}

猜你喜欢

转载自www.cnblogs.com/Yuhuger/p/10668374.html