题目描述
牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的AA到KK加上大小王的共5454张牌来进行的扑克牌游戏。在斗地主中,牌的大小关 系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<\text{小王}<\text{大王}3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由 nn 张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。
现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。
需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。具体规则如下:
输入格式
第一行包含用空格隔开的2个正整数 T,nT,n ,表示手牌的组数以及每组手牌的张数。
接下来 TT 组数据,每组数据 nn 行,每行一个非负整数对 a_i,b_iai,bi ,表示一张牌,其中 a_iai 表示牌的数码, b_ibi表示牌的花色,中间用空格隔开。特别的,我们用 11 来表示数码 AA, 1111 表示数码JJ, 1212 表示数码QQ, 1313 表示数码 KK;黑桃、红心、梅花、方片分别用 1-41−4 来表示;小王的表示方法为 0101 ,大王的表示方法为 0202 。
输出格式
共 TT 行,每行一个整数,表示打光第 ii 组手牌的最少次数。
输入输出样例
8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1
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
说明/提示
样例1说明
共有11组手牌,包含8张牌:方片77,方片88,黑桃99,方片1010,黑桃JJ,黑桃55,方片AA以及黑桃AA。可以通过打单顺子(方片77,方片88,黑桃99,方片1010,黑桃JJ),单张牌(黑桃55)以及对子牌(黑桃AA以及方片AA)在33次内打光。
对于不同的测试点, 我们约定手牌组数TT与张数nn的规模如下:
数据保证:所有的手牌都是随机生成的。
思路:
这题只记得当时去郑州时老师没怎么细说,就说打了搜索搞了八九十分,好像还是当年的第三题,不由得感叹,要是现在的NOIP还是这样该多好。。。。。
昨天在机房就听到zwjdd和shymm感叹出法的不常规,洛谷题解里也都是“我斗地主会玩,这题不会打”的感叹。。。。,感觉要不是打过一遍也要GG。。。
这题感觉就是搜索一个一个找,感觉和原来那个麻将好像。。。。
- 无论怎么搞,顺子都有利于我们打出5或以上的牌,一下子能少掉好多,所以顺子的优先级最高,其余的东西以后一个一个搞就好了。
- 然后可以适当的剪下枝,毕竟全是for 也怪吓人的。。。
- 如果当前的出牌数已经超过了当前最优的ans,剪了(最优性剪枝)
- 其他还有一些神奇的剪枝,如先出牌数多的,后出牌数少的,感觉这样能过,就没加。。。。
- 顺子要暴力找,不要搞什么幺蛾子。。。。就像3,4,5,6,7,6,7,8,9,10,如果用什么方法去处理有可能就变成了了最长的,这样就剩下了两张牌,但如果出两个顺子,就比上一种方案优秀,所以要暴力,也许是有什么方法可以预处理出来,但我还是太菜了
- 贪心打散牌,先出四带二,再出三带一,以此类推,这个仅可用与NOIP原题
下面是搜索顺序:
- 要注意一个地方两个王不一样,不能当一对,只有火箭可以。,然后就是暴搜,感觉就要注意一下细节。
1 #include<iostream> 2 using namespace std; 3 int T,n,ans,sum[25]; 4 void dfs(int x)//x为出牌次数 5 { 6 if (x>=ans) 7 return; 8 //顺子 9 int k=0;//单顺子 10 for (int i=3;i<=14;i++)//注意2和大小王不能考虑 11 { 12 if(sum[i]==0) k=0;//顺子断了 13 else 14 { 15 k++;//顺子长度增加 16 if(k>=5)//单顺子达到五张 17 { 18 for(int j=i;j>=i-k+1;j--) 19 sum[j]--;//出牌 20 dfs(x+1);//继续搜 21 for(int j=i;j>=i-k+1;j--) 22 sum[j]++;//回溯 23 } 24 } 25 } 26 k=0;//双顺子 27 for(int i=3;i<=14;i++) 28 { 29 if(sum[i]<=1) k=0; 30 else 31 { 32 k++; 33 if(k>=3)//双顺子达到三组 34 { 35 for(int j=i;j>=i-k+1;j--) 36 sum[j]-=2;//出牌 37 dfs(x+1); 38 for(int j=i;j>=i-k+1;j--) 39 sum[j]+=2;//回溯 40 } 41 } 42 } 43 k=0;//三顺子 //以下同理 44 for(int i=3;i<=14;i++) 45 { 46 if(sum[i]<=2) k=0; 47 else 48 { 49 k++; 50 if(k>=2)//三顺子达到两组 51 { 52 for(int j=i;j>=i-k+1;j--) sum[j]-=3; 53 dfs(x+1); 54 for(int j=i;j>=i-k+1;j--) sum[j]+=3; 55 } 56 } 57 } 58 //带牌 59 for(int i=2;i<=14;i++)//枚举有3张或4张的牌(这样才能带牌) 60 { 61 if(sum[i]<=3) 62 { 63 if(sum[i]<=2) 64 continue;//三张以下(不含三张)不能带牌 65 sum[i]-=3;//出掉用来带别人的牌 66 for(int j=2;j<=15;j++)//带单张 67 { 68 if(sum[j]<=0||j==i) 69 continue;//没有牌怎么带?? 70 sum[j]--;//出掉被带的单张 71 dfs(x+1); 72 sum[j]++;//回溯 73 } 74 for(int j=2;j<=14;j++)//带一对 75 { 76 if(sum[j]<=1||j==i) 77 continue;//没有一对怎么带? 78 sum[j]-=2;//出掉被带的一对 79 dfs(x+1); 80 sum[j]+=2;//回溯 81 } 82 sum[i]+=3;//回溯 83 } 84 else//大于3可以4带别的也可以3带别的 85 { 86 sum[i]-=3;//先用3张带别的 87 for(int j=2;j<=15;j++) //带单张 //以下原理同上 88 { 89 if(sum[j]<=0||j==i) 90 continue; 91 sum[j]--; 92 dfs(x+1); 93 sum[j]++; 94 } 95 for(int j=2;j<=14;j++) //带一对 96 { 97 if(sum[j]<=1||j==i) 98 continue; 99 sum[j]-=2; 100 dfs(x+1); 101 sum[j]+=2; 102 } 103 sum[i]+=3; 104 105 sum[i]-=4; //再用4张带别的 106 for(int j=2;j<=15;j++) //带2个单张 107 { 108 if(sum[j]<=0||j==i) 109 continue;//自己不能带自己喽 110 sum[j]--;//出被带的第一张单张牌 111 for (int k=2;k<=15;k++)//找第二张单张 112 { 113 if(sum[k]<=0||j==k) 114 continue; 115 sum[k]--;//出被带的第二张单张牌 116 dfs(x+1); 117 sum[k]++;//回溯 118 } 119 sum[j]++;//回溯 120 } 121 for(int j=2;j<=14;j++)//带2个对儿 122 { 123 if(sum[j]<=1||j==i) 124 continue; 125 sum[j]-=2;//出被带的第一对牌 126 for(int k=2;k<=14;k++) 127 { 128 if(sum[k]<=1||j==k) 129 continue; 130 sum[k]-=2;//出被带的第二对牌 131 dfs(x+1); 132 sum[k]+=2;//回溯 133 } 134 sum[j]+=2;//回溯 135 } 136 sum[i]+=4;//回溯 137 } 138 } 139 //把剩下的牌出完 140 for(int i=2;i<=15;i++) 141 { 142 if(sum[i]) 143 { 144 x++; 145 } 146 } 147 ans=min(ans,x); 148 } 149 int main() 150 { 151 scanf("%d%d",&T,&n); 152 while(T--) 153 { 154 ans=1<<30;//搞大一点 155 int x,y; 156 memset(sum,0,sizeof sum);//多次询问,记得清零 157 for(int i=1;i<=n;i++) 158 { 159 scanf("%d%d",&x,&y); 160 if (x==0) 161 { 162 sum[15]++;//把两张王存在一起(但是带牌的时候注意不要做对儿) 163 } 164 else 165 if(x==1) 166 { 167 sum[14]++;//由于A的牌值大所以往后放 168 } 169 else sum[x]++;//其他牌存在相应位置 170 } 171 dfs(0);//开始暴搜 172 printf("%d\n",ans); 173 } 174 }
然后就可以把这道NOIP的简单题拿满啦!!!!!
然后,洛谷就有了对这题数据不服的一群大佬搞了这题(传送门)
然后去交了一发,就出事了。。。。。
能卡成这样,还是有些惊喜的。。。。。
由此可见:
- NOIP数据水的可怕----陈彦儒老师最后一天原话
- 用心出题目,用脚造数据--------zymdalao听完后的感叹
对于增强版,他主要就把贪心给卡了!!
对于原题像
4 4 4 8 8 8 K K K
会出三次三张牌的,然而可以这样出 4 4 4 8 8和 K K K 8这样就只要两步。
不用贪心,还能用啥?然后去看了看题解,发现了是DP,方程式还不是一般的多(蒟蒻表示推不出来)!!!!然后就在一对DP中发现了还有用贪心的(还是对的!!)
- 同样还是贪心打散牌,这也是不超时的原因。对于上面的贪心就会存在一些问题,增强后要考虑拆牌,王,也算是单排,可以当对子出。
- 拆牌(重点): 什么时候可以拆呢?
- 被四带的时候不能拆,这样可能会多出一次。
- 在单牌和对牌很多时,三张和炸弹不可以拆,会多打,可能多很多次。
- 反过来,单牌和对牌不是很多时,不就可以拆了吗?当单牌和对牌比三张和炸弹少时,就可以拆了。。。
- 证明(没什么用):这时不拆会没得带,例如单牌只能单出,如果把三张拆成一单一对,让其余和炸弹一起走,就会少一步
- 举个栗子: 3 3 3 +7 and 9 9 9 and 5 5 5 5 ----->3 3 3 +9 9 and 5 5 5 5+9+7
原来 3步 后来 2步
四张是一样的。
愉快的代码环节(因为尝试新的打法,所以和原题的打法不太一样)