麻將總結

首先,麻將可以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的等差數列。

例題:

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