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;
}
posted @ 2019-04-08 07:41  Yuhuger  阅读(389)  评论(0编辑  收藏  举报