以下是我个人的理解,当然也是借鉴了其他大佬的代码,这里我通过注释的方式讲解一下思路

#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带。满足深度搜索。