题目如下:
https://www.luogu.org/problemnew/show/P2540
非常繁锁的题干令人慌乱,不知如何下手。
那么静下心来分析这道题;
问题:
手里一套扑克牌,如何规划出牌策略,以最快的次数将其打完?
考虑的因素太多太多,再加上顺子这一恶心的规则,没有斗地主千场以上经验的人真的不知所措。
解决方案(思路):
每当人脑无法想象如何处理的时候,就自然而然的想到暴力搜索,顺子当然是好搜的,按照牌的顺序就行,可是其他组合如何考虑?
由于其他种类的方案是死的不像顺子那样复杂多变,而且仔细想想是有递推关系的。
比如手里有4个A,三个3,那么当前将牌出完的次数可以由 手里只有三个3递推过来(很明显,先出三个三,再出四个A),还可以由 手里有 一个三递推(先出一个3,再出四带二),等等;
所以显然可以用dp来计算出不管顺子的情况,正常出牌每种方案出完牌的最少次数。
其中需要注意的是,不仅能出牌还能拆牌
如四个A可以当一个炸弹出,也可以拆成3个A,和一个A,还可拆成两个对A或四个A,具体如下
dp[i][j][k][l]表示有i个炸弹,j个三张牌的,k个对子,l个单牌的情况将牌打完的最小次数
void DPinit(){
memset(dp,0x3f,sizeof(dp));
dp[0][0][0][0]=0;
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
for(int k=0;k<=n;k++){
for(int l=0;l<=n;l++){
if(i){
int minn=INF;
if(k>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-2][l]+1);//四带两个对子
if(l>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l-2]+1);//四带二不同单牌
if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-1][l]+1);//四带两个相同的单牌,如AAAA+33
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j+1][k][l+1]);//四拆成三和一
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k+2][l]);//四拆成两个对子
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l+4]);//四拆成四个单
if(i>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-2][j][k][l]+1);//四带两个一样的对子 如AAAA+33+33
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l]+1);//炸弹甩
}
if(j){
if(l>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l-1]+1);//三带一
if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k-1][l]+1);//三带二
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l]+1);//三直接甩
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k+1][l+1]);//三拆对子+单
}
if(k){
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l+2]);//对子拆成两单;
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l]+1);//出对子
}
if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k][l-1]+1);//单
}
}
}
}
}
那么处理完不带顺子的情况,我们就可以轻松地只搜索顺子,操作如下
1.ans=当前状况不用顺子的情况所需次数。(王牌特殊处理一下,就当做单张出或者当对子)
2.枚举顺子的情况,更新ans,逐步;
代码如下
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define INF 99999999
#include<algorithm>
using namespace std;
int p[20],shun[5]={0,5,3,2},T,n,sumking,dp[25][25][25][25],sum[5],ans;
void DPinit(){
memset(dp,0x3f,sizeof(dp));
dp[0][0][0][0]=0;
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
for(int k=0;k<=n;k++){
for(int l=0;l<=n;l++){
if(i){
int minn=INF;
if(k>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-2][l]+1);//四带两个对子
if(l>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l-2]+1);//四带二不同单牌
if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-1][l]+1);//四带两个相同的单牌,如AAAA+33
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j+1][k][l+1]);//四拆成三和一
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k+2][l]);//四拆成两个对子
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l+4]);//四拆成四个单
if(i>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-2][j][k][l]+1);//四带两个一样的对子 如AAAA+33+33
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l]+1);//炸弹甩
}
if(j){
if(l>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l-1]+1);//三带一
if(k>=1) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k-1][l]+1);//三带二
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l]+1);//三直接甩
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k+1][l+1]);//三拆对子+单
}
if(k){
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l+2]);//对子拆成两单;
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l]+1);//出对子
}
if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k][l-1]+1);//单
}
}
}
}
}
int check(int t1,int t2,int t3,int t4,int sumk){
if(sumk==2) return min(dp[t4][t3][t2][t1]+1,dp[t4][t3][t2][t1+2]);
else return sumk==1?dp[t4][t3][t2][t1+1]:dp[t4][t3][t2][t1];
}
void dfs(int step){
if(step>=ans) return;
memset(sum,0,sizeof(sum));
for(int i=2;i<=14;i++) sum[p[i]]++;
ans=min(ans,step+check(sum[1],sum[2],sum[3],sum[4],sumking));
for(int k=1;k<=3;k++){
for(int i=3;i<=14;i++){
int j;
for(j=i;j<=14&&p[j]>=k;j++){
p[j]-=k;
if(j-i+1>=shun[k]) dfs(step+1);
}
for(j--;j>=i;j--) p[j]+=k;
}
}
}
int main(){
scanf("%d%d",&T,&n);
DPinit();
while(T--){
memset(p,0,sizeof(p));
sumking=0;
for(int i=1;i<=n;i++){
int siz,col; scanf("%d%d",&siz,&col);
if(siz==0) sumking++;
else if(siz==1) p[14]++;
else p[siz]++;
}
ans=INF;
dfs(0);
printf("%d\n",ans);
}
return 0;
}
IGNB!!!