P4050 [JSOI2007]麻将

传送门

怎么好像没什么人写 $dp$ ...?

设 $f[i][j][k][0/1]$ 表示当前处理完前 $1$ 到 $i$ 的数,上一位开始的顺子有 $j$ 个,当前位开始的顺子有 $k$ 个,是否已经有雀头,的情况下能不能胡

因为连续三个顺子其实等价于三个刻字,所以我们只要考虑顺子小于 $3$ 的情况,即 $j,k$ 都小于 $3$

然后枚举下一位的顺子数量并把当前位的顺子补好,剩下的全做刻字,顺便处理一下雀头就行

然后枚举每种可能的听牌,跑一遍 $dp$ 看看有没有解

具体怎么 $dp$ 也挺好想的,看代码注释吧...

复杂度 $O(不会算)$,反正跑得飞快

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=507;
int n,m,a[N];
int f[N][3][3][2];
bool work()
{
    memset(f,0,sizeof(f));
    f[0][0][0][0]=1;
    for(int i=0;i<n;i++)//当前处理完第i位,考虑下一位
        for(int j=0;j<3;j++)//以上一位开头有j个顺子
            for(int k=0;k<3;k++)//当前为开头有k个顺子
                for(int p=0;p<2;p++)//当前是否有雀头
                {
                    if(!f[i][j][k][p]) continue;//剪枝
                    for(int l=0;l<=min(a[i+1]-j-k,2);l++)//枚举下一位顺子数量
                        if((a[i+1]-j-k-l)%3==0)//剩下的全做刻字
                            f[i+1][k][l][p]=1;
                    if(!p)//考虑留两个做雀头
                        for(int l=0;l<=min(a[i+1]-j-k-2,2);l++)//同上
                            if((a[i+1]-j-k-l-2)%3==0)
                                f[i+1][k][l][1]=1;
                }
    return f[n][0][0][1];//看看最后有没有解
}
vector <int> ans;
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m*3+1;i++) a[read()]++;
    for(int i=1;i<=n;i++)//枚举听牌
    {
        a[i]++;
        if(work()) ans.push_back(i);
        a[i]--;
    }
    int len=ans.size();
    if(!len) { printf("NO
"); return 0; }
    for(int i=0;i<len;i++) printf("%d ",ans[i]);
    printf("
");
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/11433012.html