• 麻将总结


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

    例题:

  • 相关阅读:
    【笔记】二进制文件
    vs2015+64位win10系统ceres-solver编译
    python
    感受函数式编程-scala
    R语言diagram包画订单状态流图
    virtualbox下Centos6.5桥接模式上网配置方法
    配置对IIS上tabular的 HTTP 访问
    centos6.5下逻辑卷操作
    centos6.5下磁盘创建交换分区
    centos6.5下磁盘分区及挂载
  • 原文地址:https://www.cnblogs.com/lnzwz/p/12649746.html
Copyright © 2020-2023  润新知