洛谷 2540 斗地主增强版
Description
牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。
现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。
需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。
具体规则如下:
在此题中认为两个王不能组成对子牌
Input
第一行包含用空格隔开的2个正整数T和n,表示手牌的组数以及每组手牌的张数。
接下来T组数据,每组数据n行,每行一个非负整数对aibi表示一张牌,其中ai示牌的数码,bi表示牌的花色,中间用空格隔开。特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。
Output
共T行,每行一个整数,表示打光第i手牌的最少次数。
Sample
1 8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1
3
1 17
12 3
4 3
2 3
5 4
10 2
3 3
12 2
0 1
1 3
10 1
6 2
12 1
11 3
5 2
12 4
2 2
7 2
6
Hints
样例1说明
共有1组手牌,包含8张牌:方片7,方片8,黑桃9,方片10,黑桃J,黑桃5,方片A以及黑桃A。可以通过打单顺子(方片7,方片8,黑桃9,方片10,黑桃J),单张牌(黑桃5)以及对子牌(黑桃A以及方片A)在3次内打光。
对于前20个测试点, 我们约定手牌组数T与张数n的规模如下:
数据不保证所有的手牌都是随机生成的。
好像很早就听说这道神奇的题了,我以为这是一道像模拟的搜索题,就开始傻傻地模拟,但是难过地发现样例就超时了
T了很久,去看了题解,题解是搜索+贪心,打了一会儿,De了很久的bug,终于过了,写题解的时候想不通那个贪心,想了很久,后来终于发现那个贪心有bug
出完顺子以后,不能直接得出答案,因为有些4,3什么的可以拆一下,但是不同情况下的拆法不一样
好像写了4,5个版本,写得要疯掉了●︿●
我发现了自己为什么要T,其实是搜索的时候重复了,确定出牌方法以后,顺序就没有影响了,所以每次枚举的时候不需要把所有出牌方法都搜一遍
Solution
搜索的时候状态不能重复,要按照某一个顺序
✿按照牌的大小顺序搜索
枚举当前这种类型牌以什么形式出
如果是顺子,那么以当前牌为起点,往后找是否能构成顺子
三带二...什么的,可以先不枚举带的那张(对)牌,先记录一下,单牌,对子,三张牌,四张牌各出了多少次,到最后再处理,这样就可以只枚举一种牌一次出几张(优化一下,变成把一个数拆分成什么,因为2总是优于2个1,所以不考虑把2拆成1)
(为4分配2,1,为3分配...什么的,处理的时候,优先为4找两单,两双,一双(相当于两单),然后为3找一单,一双,在答案里减去)
(注意一种牌可能会分几次被出出去,所以如果一次出牌后,还没有出完,下一次要继续搜索这一种牌)
✿按照出牌的顺序出
我没有用这种方法写过,看到有大佬写了,跑得有点慢
每次枚举一种类型的时候,如果互不相关的话,只搜一个(因为答案跟出牌顺序无关),举个栗子,枚举顺子的话,只找到第一个最长的顺子,然后枚举长度,保证与这个顺子相关的顺子都考虑到了,而无关的顺子不需要管,下一次也可以被枚举到
枚举其它牌的方法同上,一个剪枝,如果剩下的单牌,对子什么的可以被出过的三张牌,四张牌带走,就不用往下搜了,直接更新答案
注意细节
比如,大王小王特殊处理
不要把四带两对看成四带一对了
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<queue> #define inf 100000001 using namespace std; int pai[21],use[6],res=inf; int read() { int ans=0,f=1;char ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) {ans=ans*10+ch-'0';ch=getchar();} return ans*f; } void search(int th,int v) { if(th>14) { int ne=v,a=use[1],b=use[2],c=use[3],d=use[4]; if(a==2&&b==1&&c==1&&d==1) { int debug=1; } if(d>=(a>>1)) ne-=(a>>1)<<1,d-=(a>>1),a&=1; else ne-=d<<1,a-=d<<1,d=0; if(d>=(b>>1)) ne-=(b>>1)<<1,d-=(b>>1),b&=1; else ne-=d<<1,b-=d<<1,d=0; if(d>=b) ne-=b,d-=b,b=0; if(c>=a) ne-=a,c-=a,a=0; else ne-=c,a-=c,c=0; if(c>=b) ne-=b,c-=b,b=0; else ne-=c,b-=c,c=0; res=ne<res? ne:res; return; } if(!pai[th]) { search(th+1,v); return; } int now=-1; if(th>=3) //顺子 { for(int i=th;i<=14;i++) { if(pai[i]<3) break; now=i; pai[i]-=3; if(i-th+1>=2) search(th,v+1); } for(int i=th;i<=now;i++) pai[i]+=3; now=-1; ////////// for(int i=th;i<=14;i++) { if(pai[i]<2) break; now=i; pai[i]-=2; if(i-th+1>=3) search(th,v+1); } for(int i=th;i<=now;i++) pai[i]+=2; now=-1; for(int i=th;i<=14;i++) { if(!pai[i]) break; now=i; pai[i]--; if(i-th+1>=5) search(th,v+1); } for(int i=th;i<=now;i++) pai[i]++; } if(pai[th]==4) { pai[th]-=4; use[4]++;search(th+1,v+1);use[4]--; use[3]++;use[1]++;search(th+1,v+2);use[3]--;use[1]--; use[2]+=2;search(th+1,v+2);use[2]-=2; pai[th]+=4; } else if(pai[th]==3) { pai[th]-=3; use[3]++;search(th+1,v+1);use[3]--; use[2]++;use[1]++;search(th+1,v+2);use[2]--;use[1]--; //相当于出了两次,v要+2 pai[th]+=3; } else if(pai[th]==2) { pai[th]-=2; use[2]++;search(th+1,v+1);use[2]--; pai[th]+=2; } else if(pai[th]==1) { pai[th]--; use[1]++;search(th+1,v+1);use[1]--; pai[th]++; } } int main() { // freopen("testdata21.in","r",stdin); // freopen("wo.out","w",stdout); int t,n,num,co; t=read();n=read(); for(int j=1;j<=t;j++) { if(j==13) { int debug=1; } res=inf; memset(pai,0,sizeof(pai)); //傻傻忘清零 memset(use,0,sizeof(use)); // for(int i=1;i<=n;i++) { num=read();co=read(); if(num==1) pai[14]++; else if(num==0) pai[co-1]++; else pai[num]++; } if(pai[0]&&pai[1]) { pai[0]--,pai[1]--,search(2,1); //不能放在 pai[0]++,pai[1]++,search(0,0); // } else search(0,0); printf("%d\n",res); } return 0; } /* 1 10 1 1 1 1 1 1 1 1 3 1 3 1 3 1 5 1 5 1 7 1 */