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\n"); return 0; } for(int i=0;i<len;i++) printf("%d ",ans[i]); printf("\n"); return 0; }