麻将总结

首先,麻将可以DP。
用DP枚举顺子,因为3个顺子可以变成3个刻子,因此同一位置的顺子数目不会超过2。
这样,在DP时,记录前两个位置选择的顺子个数,即可。状态数为9。

将9个状态的值进行压缩(可以增加顺子和刻子的数量),并记录转移,可以得到麻将自动机。
用这个自动机可以判断是否胡牌等。
在胡牌种类计数等问题上,需要dp套dp,可以记录这个自动机。
建立麻将自动机的大致代码(这里有七对的判断):
走出自动机则意味着胡牌。

void insert(int f[2][3][3],int &f2,int g[2][3][3],int g2,int x)
{
	for(int i=0;i<2;i++)
	{
		for(int a=0;a<3;a++)
		{
			for(int b=0;b<3;b++)
			{
				f[i][a][b]=-inf;
				for(int c=0;c<3;c++)
				{
					if(a+b+c<=x&&g[i][c][a]+c>f[i][a][b])
						f[i][a][b]=g[i][c][a]+c;
					if(a+b+c<=x-3&&g[i][c][a]+c+1>f[i][a][b])
						f[i][a][b]=g[i][c][a]+c+1;
				}
				if(i==1)
				{
					for(int c=0;c<3;c++)
					{
						if(a+b+c<=x-2&&g[0][c][a]+c>f[i][a][b])
							f[i][a][b]=g[0][c][a]+c;
					}
				}
				if(f[i][a][b]>4)f[i][a][b]=4;
				if(f[i][a][b]<0)f[i][a][b]=-inf;
			}
		}
	}
	f2=g2+(x>=2);
}
ll getha(int f[2][3][3],int f2)
{
	if(f2>=7)return -1;
	ll jg=0;
	for(int i=0;i<2;i++)
	{
		for(int a=0;a<3;a++)
		{
			for(int b=0;b<3;b++)
			{
				if(i==1&&f[i][a][b]==4)
					return -1;
				jg=jg*6+(f[i][a][b]==-inf?5:f[i][a][b]);
			}
		}
	}
	return jg*7+f2;
}
map<ll,int> mp;
int check(int f[2][3][3],int f2)
{
	ll ha=getha(f,f2);
	if(ha==-1)
		return -1;
	if(mp.count(ha))
		return mp[ha];
	return 0;
}
int trs[MN][5],sl=0;
int dfs(int g[2][3][3],int g2)
{
	int rt,f[2][3][3],f2;
	if((rt=check(g,g2)))
		return rt;
	mp[getha(g,g2)]=rt=++sl;
	for(int x=0;x<=4;x++)
	{
		insert(f,f2,g,g2,x);
		trs[rt][x]=dfs(f,f2);
	}
	return rt;
}

如果仅仅判断能否全部划分为顺子和刻子,可以贪心:从头开始,添加顺子将数量调整为3的倍数,在最后判断剩余的2个是否都是3的倍数即可。
用这种方法得到的顺子数是最少的。
若要得到最多的顺子数,可以依次在每个位置上尽可能多的添加 3的倍数 个顺子。
容易证明,可行的顺子数是一个公差为3的等差数列(因此求出最值即可)。

在添加了对子的要求时,使用dp只要增加一个0/1状态即可。
如果使用上述方法,需要求出前缀和后缀贪心后的状态,枚举对子位置后将前缀后缀合并。

int jian(int to[100010],int i)
{
	int t=min(to[i],to[i+1],to[i+2]);
	t=(t/3)*3;
	to[i]-=t;
	to[i+1]-=t;
	to[i+2]-=t;
	return t;
}
struct SLe
{
	int a,b,c,d;
	ll he;
};
struct SRi
{
	int a,b,c,d;
	ll he;
};
ll mermin2(SLe&x,SRi&y,int&z)
{
	if(x.he==-1||y.he==-1)
		return -1;
	ll ans=x.he+y.he;
	int t=x.c%3;
	x.c-=t;x.d-=t;z-=t;ans+=t;
	if(x.d<0)return -1;
	t=x.d%3;
	x.d-=t;z-=t;y.a-=t;ans+=t;
	if(z<0)return -1;
	t=z%3;
	z-=t;y.a-=t;y.b-=t;ans+=t;
	if(y.a<0||y.b<0||y.a%3||y.b%3)
		return -1;
	return ans;
}
ll mermin(SLe x,SRi y,int z)
{
	return mermin2(x,y,z);
}
ll mkmax(int sz[10],int n)
{
	ll ans=0;
	for(int i=0;i<n-2;i++)
		ans+=jian(sz,i);
	return ans;
}
ll mermax(SLe x,SRi y,int z)
{
	ll ans=mermin2(x,y,z);
	if(ans==-1)return -1;
	int t[10]={x.a,x.b,x.c,x.d,z,y.a,y.b,y.c,y.d};
	return ans+mkmax(t,9);
}

容易证明,在有对子时,可行的顺子数仍然是一个公差为3的等差数列。

例题:

posted @ 2020-04-06 21:31  lnzwz  阅读(480)  评论(0编辑  收藏  举报