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