zoukankan      html  css  js  c++  java
  • 2015D1T3 斗地主&斗地主加强版

    2015D1T3 斗地主

    problem

    斗地主是一种使用黑桃、红心、梅花、方片的(A)(K)加上大小王的共(54)张牌来进行的扑克牌游戏。在斗地主中,牌的大小关 系根据牌的数码表示如下:(3<4<5<6<7<8<9<10<J<Q<K<A<2< ext{小王}< ext{大王}),而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由 (n) 张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。 现在对于若干组手牌,分别最少需要多少次出牌可以将它们打光。 需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。具体规则如下:

    第一行包含用空格隔开的2个正整数 (T,n) ,表示手牌的组数以及每组手牌的张数。 接下来 (T) 组数据,每组数据 (n) 行,每行一个非负整数对 (a_i,b_i) ,表示一张牌,其中 (a_i) 表示牌的数码, (b_i) 表示牌的花色,中间用空格隔开。特别的,我们用 (1) 来表示数码 (A)(11) 表示数码$ J$, (12) 表示数码$ Q$, (13) 表示数码 (K);黑桃、红心、梅花、方片分别用 (1-4) 来表示;小王的表示方法为 (01) ,大王的表示方法为 (02)

    输出共 (T) 行,每行一个整数,表示打光第 (i) 组手牌的最少次数。

    solution


    一个奇妙的爆搜。我觉得这是一道绝妙的搜索进阶模板。其实代码特别好懂。这种牌类游戏可能记录每种牌的个数会比记录点数好一些。

    code

    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <iostream>
    #include <queue>
    using namespace std;
    int read(){
    	int a=0,op=1;char c=getchar();
    	while(c>'9'||c<'0') {if(c=='-') op=-1;c=getchar();}
    	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
    	return a*op;
    }
    int T,ans,n,sum[25];
    void dfs(int x){//x为出牌次数
    	if(x>=ans) return ;
    	int k=0;
    	for(int i=3;i<=14;i++){
    		if(sum[i]==0) k=0;
    		else {
    			k++;
    			if(k>=5){
    				for(int j=i;j>=i-k+1;j--) sum[j]--;
    				dfs(x+1);
    				for(int j=i;j>=i-k+1;j--) sum[j]++;
    			}
    		}
    	}
    	k=0;//双顺子
    	for(int i=3;i<=14;i++){
    		if(sum[i]<=1) k=0;
    		else {
    			k++;
    			if(k>=3){
    				for(int j=i;j>=i-k+1;j--) sum[j]-=2;
    				dfs(x+1);
    				for(int j=i;j>=i-k+1;j--) sum[j]+=2;
    			}
    		}
    	}
    	k=0;//三顺子
    	for(int i=3;i<=14;i++){
    		if(sum[i]<=2) k=0;
    		else {
    			k++;
    			if(k>=2){
    				for(int j=i;j>=i-k+1;j--) sum[j]-=3;
    				dfs(x+1);
    				for(int j=i;j>=i-k+1;j--) sum[j]+=3;
    			}
    		}
    	}
    	k=0;//带牌
    	for(int i=2;i<=14;i++){
    		if(sum[i]<=3){
    			if(sum[i]<=2) continue;
    			sum[i]-=3;
    			for(int j=2;j<=15;j++){
    				if(sum[j]<=0||j==i) continue;//没有牌带
    				sum[j]--;
    				dfs(x+1);
    				sum[j]++;
    			}
    			for(int j=2;j<=15;j++){
    				if(sum[j]<=1||j==i) continue;//没有牌带
    				sum[j]-=2;
    				dfs(x+1);
    				sum[j]+=2;
    			}
    			sum[i]+=3;
    		}
    		else{
    			sum[i]-=3;
    			for(int j=2;j<=15;j++){
    				if(sum[j]<=0||j==i) continue;
    				sum[j]--;dfs(x+1);sum[j]++;
    			}
    			for(int j=2;j<=14;j++){
    				if(sum[j]<=1||j==i) continue;
    				sum[j]-=2;dfs(x+1);sum[j]+=2;
    			}
    			sum[i]+=3;
    			sum[i]-=4;
    			for(int j=2;j<=15;j++){
    				if(sum[j]<=0||j==i) continue;//不能自己带自己
    				sum[j]--;
    				for(int k=2;k<=15;k++){
    					if(sum[k]<=0||j==k) continue;
    					sum[k]--;
    					dfs(x+1);
    					sum[k]++;
    				}
    				sum[j]++;
    			}
    			for(int j=2;j<=14;j++){
    				if(sum[j]<=1||j==i) continue;
    				sum[j]-=2;
    				for(int k=2;k<=14;k++){
    					if(sum[k]<=1||j==k) continue;
    					sum[k]-=2;
    					dfs(x+1);
    					sum[k]+=2;
    				}
    				sum[j]+=2;
    			}
    			sum[i]+=4;
    		}
    		
    	}
    	for(int i=2;i<=15;i++) if(sum[i]) x++;
    	ans=min(ans,x);
    }
    int main(){
    	T=read(),n=read();
    
    	while(T--){
    		ans=0x7fffffff;
    		int x,y;
    		memset(sum,0,sizeof(sum));
    		for(int i=1;i<=n;i++){
    			x=read(),y=read();
    			if(x==0) sum[15]++;
    			else if(x==1) sum[14]++;
    			else sum[x]++;
    		}
    		dfs(0);
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    斗地主加强版

    原样交上去82分。有WA有TLE。

    暴力搜顺子

    为什么顺子要暴力?并不是出越长的顺子越好,如果你有一组手牌,3,4,5,6,7,6,7,8,9,10,你一下把最长的出了去,你会单两张牌,不如出两个顺子,所以顺子要暴力。

    贪心打散牌

    这是核心所在,也是不超时的原因。
    可以先统计一下不同牌个数的组数,然后再出牌,
    那如何打出最优解?
    首先一定要先出四带二,再出三带一,这是很容易想到的,因为四带可以带走两张。
    这样写正的行了吗?
    当然不行,原题的随机数额可以过,增强版的必须考虑拆牌,而且还要考虑王炸的特殊情况,王算单牌,但可以当对出,明确这一点。

    拆牌

    什么时候该拆牌呢,对牌除了四带的情况不能拆,因为拆了可能多打一手牌。
    仔细想想,三张和炸在单牌和对牌很多的时候是不能拆的,拆了就多大。
    当单牌和对牌数的和小于三张和炸的和,这就可以拆了
    因为这时不拆的话没得带,只能单出,如果把三张拆成一单和一对,让其余的三张和炸带走,就会少一步。
    四张的同理。

    #include <bits/stdc++.h>
    using namespace std;
    int n,T,d[5],c[20],a,b,kin,ans,tot,mn;
    int read(){
    	int a=0,op=1;char c=getchar();
    	while(c>'9'||c<'0') {if(c=='-') op=-1;c=getchar();}
    	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
    	return a*op;
    }
    int gao()
    {
        int res=0,e[5];memcpy(e,d,sizeof(e));//做完之后要还原,备份一份d数组
        while(d[4])
        {
            if(d[1]<2&&!d[2]) break;//不能四带二了
            if(d[1]>=2) d[4]--,d[1]-=2,res++;//带两个单牌
            else if(d[2]>=2) d[4]--,d[2]-=2,res++;//带两个对子
            else if(d[2]==1) d[4]--,d[2]--,res++;//带一个对子(优先级最低)
        }
        while(d[3])
        {
            if(!d[1]&&!d[2]) break;//不能三带了
            if(d[1]) d[3]--,d[1]--,res++;//带一个单牌
            else if(d[2]) d[3]--,d[2]--,res++;//带一个对子
        }
        int g=d[1]+d[2]+d[3]+d[4]+res;//总出牌次数
        memcpy(d,e,sizeof(e));return g;//还原
    }
    void dfs2()
    {
        tot=min(tot,gao());//即时统计答案
        if(d[3]) d[3]--,d[1]++,d[2]++,dfs2(),d[3]++,d[1]--,d[2]--;//把三张牌拆成一个单牌和一个对子(33344445555)
        if(d[4]) d[4]--,d[2]++,d[2]++,dfs2(),d[4]++,d[2]--,d[2]--;//把四张牌拆成两个对子(33334444)
        if(d[4]) d[4]--,d[1]++,d[3]++,dfs2(),d[4]++,d[1]--,d[3]--;//把四张牌拆成一个单牌和一个三对(33334444556)
    }
    void dfs1(int val)
    {
        memset(d,0,sizeof(d));for(int i=2;i<=16;i++) d[c[i]]++;//统计不同大小的牌的出现次数
        tot=1e9;dfs2();ans=min(ans,val+tot);//即时更新答案
        for(int i=3;i<=10;i++) if(c[i]>=1) for(int j=i+1;j<=14;j++)//出单顺
        {
            if(c[j]<1) break;if(j-i<4) continue;
            for(int k=i;k<=j;k++) c[k]-=1;dfs1(val+1);for(int k=i;k<=j;k++) c[k]+=1;
        }
        for(int i=3;i<=12;i++) if(c[i]>=2) for(int j=i+1;j<=14;j++)//出双顺
        {
            if(c[j]<2) break;if(j-i<2) continue;
            for(int k=i;k<=j;k++) c[k]-=2;dfs1(val+1);for(int k=i;k<=j;k++) c[k]+=2;
        }
        for(int i=3;i<=13;i++) if(c[i]>=3) for(int j=i+1;j<=14;j++)//出三顺
        {
            if(c[j]<3) break;if(j-i<1) continue;
            for(int k=i;k<=j;k++) c[k]-=3;dfs1(val+1);for(int k=i;k<=j;k++) c[k]+=3;
        }
    }
    int main()
    {
        for(scanf("%d%d",&T,&n);T--;)
        {
            memset(c,0,sizeof(c));mn=1e9;
            for(int i=1;i<=n;i++)
            {
                scanf("%d%d",&a,&b);
                if(a==0&&b==1) c[15]++;
                else if(a==0&&b==2) c[16]++;//大小王
                else if(a==1) c[14]++;
                else if(a>1) c[a]++;//对A进行处理,方便出顺子
            }
            kin=min(c[15],c[16]);
            c[15]-=kin;c[16]-=kin;//出王炸(333大小王)
            ans=1e9;dfs1(0);mn=min(mn,kin+ans);
            c[15]+=kin;c[16]+=kin;//不出王炸(3333大小王)
            ans=1e9;dfs1(0);mn=min(mn,ans);
            printf("%d
    ",mn);
        }
    }
    
  • 相关阅读:
    Java-集合类源码List篇(二)
    规范输入
    Java 构造方法
    c语言函数实现交换两个数的值
    2015北京宇信易诚科技面试题
    Java中的访问控制
    2015大连华信校园招聘面试题--堆栈
    2015大连华信面试题OSI七层模型
    2015大连华信面试题二叉树、遍历
    2015大连华信面试题MVC框架
  • 原文地址:https://www.cnblogs.com/liuziwen0224/p/2015d1t3.html
Copyright © 2011-2022 走看看