以下是我个人的理解,当然也是借鉴了其他大佬的代码,这里我通过注释的方式讲解一下思路
#include<bits/stdc++.h>
using namespace std;
const int card[4]={0,5,3,2};
const int Max=16;
int n,m,ans,x,y,t;
int a[Max],sum[Max];
int f(){
int t=0;
memset(sum,0,sizeof(sum));
for(int i=0;i<=13;i++)sum[a[i]]++;
while(sum[4]&&sum[2]>=2)t++,sum[4]-=1,sum[2]-=2;//4带2
while(sum[4]&&sum[1]>=2)t++,sum[4]-=1,sum[1]-=2;//4带1
while(sum[3]&&sum[2]>=1)t++,sum[2]-=1,sum[3]-=1;//3带2
while(sum[3]&&sum[1]>=1)t++,sum[1]-=1,sum[3]-=1;//3带1
return t+sum[4]+sum[3]+sum[2]+sum[1];//返回我们打带子的次数以及炸弹,3张牌,对子牌,还有单打一共的次数。
} void dfs(int dep){
if(dep>ans)return;
for(int same=3;same>=1;same--){//顺子,即单顺子,双顺子,三顺子
for(int i=2;i<=13;i++){//由于全体左移了一位,a[2]==3,a[13]==14(即A)。
int j=i;
while(a[j]>=same&&j<=14)j++;j--;//首先判断我们能不能打顺子(例如3顺子),且可以出现3张2,故j<=14(将2看做14)
if(j-i+1<card[same])continue;//如果满足顺子,此条忽略,不满足回到循环,循环继续。
for(int k=i;k<=card[same]+i-2;k++)a[k]-=same;//先每次打出card[same]-1张,还差一个组成顺子(比如333444,先打333,还差 444)。
for(int k=i+card[same]-1;k<=j;k++)a[k]-=same,dfs(dep+1);//我们要保证我们打出的顺子是最完美的,所以,在这次的循环里面补全之前的顺子,然后递归回去,此时dep+1(因为我们已经打出了一组顺子),递归回去再打一组顺子,反复循环,直到没有顺子(因为顺子 比其他更优先)。
for(int k=i;k<=j;k++)a[k]+=same;//此时之前已经打完了所有的顺子,现在补回来(因为递归的时候ans已经是打顺子之后再打其他优先级需要多少次打完的数字了,已经枚举完顺子得到了最少的次数了,如果不将牌组补回来的话,例如:7,8,9,10,11,5,1,1。我们之前已经打了7 8 9 10 11,,现在只剩5 1 1了而对于f()而言sum[2]+sum[1]=2,也就是说2<3,答案将会是2,而正确答案是3,所以为了防止出现这种错误,我们要补回来)
}
}
//cout<<dep<<endl;
//cout<<ans<<endl;输出dep,ans这俩行是就是判断补不回来的后果的
ans=min(ans,dep+f());//返回最小的次数
}
int main(){
cin>>t>>n;
while(t--){
memset(a,0,sizeof(a));
ans=n;//初始值,即发多少张打多少次,全部当做单子出,此时ans最大
for(int i=1;i<=n;i++){
cin>>x>>y,x=x==1?14:x;//将A看做14,便于计算
if(x!=0)
--x;//因为少了一个1,全体左移一位
a[x]++;//记录手上有多少张该牌(即多少张3之类)
}
dfs(0);//初始时我们打了0次顺子,即初始为0
cout<<ans<<endl;
}
return 0;
} //若次数最少,则应默认顺序为顺子->4带->3带。满足深度搜索。