UVALive 5907 —— Tichu(搜索)

题目在此


题意:前面废话忽略,重点是,手上初始有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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值