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