题意:前面废话忽略,重点是,手上初始有13张牌(输入),然后按照一定的规则可以将牌组合起来,每张牌必须且只能用一次;
问题是要找一种方案使得组合出来的组数应尽量少,输出最少多少组,并输出任意一个方案。
每张牌上面有点数2~9 T J Q K A,规定点数按照这个顺序增大,即2是最小, A是最大的,花色用s,h,c,d代表;
题目给出6种组合规则:
1、单一张牌;
2、两张点数一样的牌;
3、三张点数一样的牌;
4、四张点数一样的牌;
5、三张点数为x和两站点数为y,x和y不同,当然相同你也找不到5张。。。
6、点数连续至少5张,如6789TJ,其实就是按照上面的点数大小顺序来;
题目保证输入的牌不会有重复。
采用搜索来解这个题目,貌似不用怎么剪枝速度也是很快的。
看起来有13张牌,逐张搜索的话每层理论是6种决策,也就是总状态数6^13,但事实上并不会出现这么多。因为你每次组合都会引发其他牌数量的减少,也就是说其实收敛得很快的,像规则5和6一次至少都去掉5张牌了。
我的做法是用一个数组f记录每种点数的张数,再用一个布尔的数组p,p[i][j]为1代表点数为i的牌出现了第j种花色(这个是为了后面输出方案)。
点数方面,为了后面枚举方便,将上面的点数按顺序映射到0~12,点数2在0位置,A在12位置,下面说到的点数都是位置了。
上面看上去是6种方案,实际可以把前四点看作一种:对于点数x,枚举取1~f[x]张牌。
单种点数的枚举和三飞二的没什么,主要是连续的枚举,假如有0~11都可以连续得到,如果先枚举0~4,接着0~5,0~6等等,个人觉得是比较麻烦了。
我的做法是直接用i从x往右扫,直到i==13或是f[i]为0,break出来,那么说明x最多连续到i-1,前面在扫的时候对那些非0都先减一;
接下来就从x~i往回枚举了,如果i-x<4就说明不足5张,不用递归,否则就递归下去,递归完了,要判断x~i-1了,此时先将f[i]++,因为现在是不取i了。(可能有点绕,看代码可能好懂些)
在搜索的时候,每次选择一种决策方案的时候,先把选择的点数做对应的修改(减少),花色不用管,决策没管到花色,然后递归进入下一层,回溯的时候在把点数加回去即可。
方案的记录,我是采用一个结构体
struct Solu{
int A, B, C;
Solu(){}
Solu(int _A, int _B, int _C){
A=_A; B=_B; C=_C;
}
}
A代表方案类型,具体如下:
A=0时:表示连续的B到C,比如6789TJ,就表示为A=0,B=4,C=9(这里记录的还是点数映射的位置)
A=1时:表示点数为B取了C张;
A=2时:表示点数为B取了三张,点数C的取了两张;
然后剩下的工作就是写好搜索函数,寻找并记录最优答案了。
最后面的输出,就通过分析结构体内A的类型做对应的输出即可。因为题目对方案的顺序没要求,所以你就随意咯,怎么输出方便就怎么来。
具体细节请参考代码:
#include<cstdio>
#include<cstring>
const int inf = 0x7fffffff;
char s[10];
char list[20]={'2','3','4','5','6','7','8','9','T','J','Q','K','A'};
char type[10]={'s','h','c','d'};
void getch(char a, char b, int& x, int& y){
for(int i=0; i<13; i++){
if(list[i]==a){
x=i;
break;
}
}
for(int i=0; i<4; i++){
if(type[i]==b){
y=i;
break;
}
}
}
int f[13];
bool p[13][4];
void print(int x){
putchar(list[x]);
//选择一个存在的花色输出,并把花色标记为0
for(int i=0; i<4; i++){
if(p[x][i]){
putchar(type[i]);
p[x][i]=0;
break;
}
}
}
struct Solu{
int A, B, C;
Solu(){}
Solu(int _A, int _B, int _C){
A=_A; B=_B; C=_C;
}
}cur[50], res[50];
//cur记录当前的方案,res记录当前的最优方案
int t, ans;
void dfs(int x, int u){
if(u>=ans) return;//小小的剪枝,如果当前组数超过全局最优则返回
if(x>=13){
if(u<ans){//更新答案
ans = u;
for(int i=0; i<u; i++) res[i] = cur[i];
}
return;
}
//当前点数张数为0,直接看下一个点数
//并且这样的写法,保证了搜索到x时,x前面的点数一定是空的或是被取完了
if(!f[x]){
dfs(x+1, u);
return;
}
int i;
//找向右的连续最大区间
for(i=x; i<13; i++){
if(!f[i]) break;
f[i]--;
}
for(--i; i>=x; i--){
if(i-x>=4){//连续超过5张的
cur[u] = Solu(0, x, i);
dfs(x, u+1);
}
f[i]++;
}
for(i=1; i<=f[x]; i++){
//同种点数取i张
f[x]-=i;
cur[u] = Solu(1, x, i);
dfs(x, u+1);
f[x]+=i;
}
if(f[x]>=3){
//取三张x和两张i
f[x]-=3;
for(i=x+1; i<13; i++){
if(f[i]>=2){
f[i]-=2;
cur[u] = Solu(2, x, i);
dfs(x, u+1);
f[i]+=2;
}
}
f[x]+=3;
}
if(f[x]>=2){
//取三张i和两张x
f[x]-=2;
for(i=x+1; i<13; i++){
if(f[i]>=3){
f[i]-=3;
cur[u] = Solu(2, i, x);
dfs(x, u+1);
f[i]+=3;
}
}
f[x]+=2;
}
}
int main(){
scanf("%d", &t);
while(t--){
int x, y;
memset(f,0,sizeof(f));
memset(p,0,sizeof(p));
for(int i=0; i<13; i++){
scanf("%s", s);
getch(s[0], s[1], x, y);
f[x]++;
p[x][y]=1;
}
ans = inf;
dfs(0,0);
printf("%d\n", ans);
for(int i=0; i<ans; i++){
if(res[i].A==0){
for(int j=res[i].B; j<=res[i].C; j++){
if(j!=res[i].B) putchar(' ');
print(j);
}
}
else if(res[i].A==1){
for(int j=0; j<res[i].C; j++){
if(j) putchar(' ');
print(res[i].B);
}
}
else{
print(res[i].B);
for(int j=0; j<2; j++){
putchar(' '); print(res[i].B);
putchar(' '); print(res[i].C);
}
}
puts("");
}
}
return 0;
}