题目描述

牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关 系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由 n 张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。

现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。

需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。具体规则如下:

输入格式

第一行包含用空格隔开的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 组手牌的最少次数。

输入输出样例

输入 #1
  1. 8
  2. 7 4
  3. 8 4
  4. 9 1
  5. 10 4
  6. 11 1
  7. 5 1
  8. 1 4
  9. 1 1
输出 #1
  3
输入 #2
    1 17
  1. 12 3
  2. 4 3
  3. 2 3
  4. 5 4
  5. 10 2
  6. 3 3
  7. 12 2
  8. 0 1
  9. 1 3
  10. 10 1
  11. 6 2
  12. 12 1
  13. 11 3
  14. 5 2
  15. 12 4
  16. 2 2
  17. 7 2
输出 #2
  6

说明/提示

样例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[];
  4. void dfs(int x)//x为出牌次数
  5. {
  6. if (x>=ans)
  7. return;
  8. //顺子
  9. int k=;//单顺子
  10. for (int i=;i<=;i++)//注意2和大小王不能考虑
  11. {
  12. if(sum[i]==) k=;//顺子断了
  13. else
  14. {
  15. k++;//顺子长度增加
  16. if(k>=)//单顺子达到五张
  17. {
  18. for(int j=i;j>=i-k+;j--)
  19. sum[j]--;//出牌
  20. dfs(x+);//继续搜
  21. for(int j=i;j>=i-k+;j--)
  22. sum[j]++;//回溯
  23. }
  24. }
  25. }
  26. k=;//双顺子
  27. for(int i=;i<=;i++)
  28. {
  29. if(sum[i]<=) k=;
  30. else
  31. {
  32. k++;
  33. if(k>=)//双顺子达到三组
  34. {
  35. for(int j=i;j>=i-k+;j--)
  36. sum[j]-=;//出牌
  37. dfs(x+);
  38. for(int j=i;j>=i-k+;j--)
  39. sum[j]+=;//回溯
  40. }
  41. }
  42. }
  43. k=;//三顺子 //以下同理
  44. for(int i=;i<=;i++)
  45. {
  46. if(sum[i]<=) k=;
  47. else
  48. {
  49. k++;
  50. if(k>=)//三顺子达到两组
  51. {
  52. for(int j=i;j>=i-k+;j--) sum[j]-=;
  53. dfs(x+);
  54. for(int j=i;j>=i-k+;j--) sum[j]+=;
  55. }
  56. }
  57. }
  58. //带牌
  59. for(int i=;i<=;i++)//枚举有3张或4张的牌(这样才能带牌)
  60. {
  61. if(sum[i]<=)
  62. {
  63. if(sum[i]<=)
  64. continue;//三张以下(不含三张)不能带牌
  65. sum[i]-=;//出掉用来带别人的牌
  66. for(int j=;j<=;j++)//带单张
  67. {
  68. if(sum[j]<=)
  69. continue;//没有牌怎么带??
  70. sum[j]--;//出掉被带的单张
  71. dfs(x+);
  72. sum[j]++;//回溯
  73. }
  74. for(int j=;j<=;j++)//带一对
  75. {
  76. if(sum[j]<=)
  77. continue;//没有一对怎么带?
  78. sum[j]-=;//出掉被带的一对
  79. dfs(x+);
  80. sum[j]+=;//回溯
  81. }
  82. sum[i]+=;//回溯
  83. }
  84. else//大于3可以4带别的也可以3带别的
  85. {
  86. sum[i]-=;//先用3张带别的
  87. for(int j=;j<=;j++) //带单张 //以下原理同上
  88. {
  89. if(sum[j]<=)
  90. continue;
  91. sum[j]--;
  92. dfs(x+);
  93. sum[j]++;
  94. }
  95. for(int j=;j<=;j++) //带一对
  96. {
  97. if(sum[j]<=)
  98. continue;
  99. sum[j]-=;
  100. dfs(x+);
  101. sum[j]+=;
  102. }
  103. sum[i]+=;
  104.  
  105. sum[i]-=; //再用4张带别的
  106. for(int j=;j<=;j++) //带2个单张
  107. {
  108. if(sum[j]<=)
  109. continue;//自己不能带自己喽
  110. sum[j]--;//出被带的第一张单张牌
  111. for (int k=;k<=;k++)//找第二张单张
  112. {
  113. if(sum[k]<=)
  114. continue;
  115. sum[k]--;//出被带的第二张单张牌
  116. dfs(x+);
  117. sum[k]++;//回溯
  118. }
  119. sum[j]++;//回溯
  120. }
  121. for(int j=;j<=;j++)//带2个对儿
  122. {
  123. if(sum[j]<=)
  124. continue;
  125. sum[j]-=;//出被带的第一对牌
  126. for(int k=;k<=;k++)
  127. {
  128. if(sum[k]<=)
  129. continue;
  130. sum[k]-=;//出被带的第二对牌
  131. dfs(x+);
  132. sum[k]+=;//回溯
  133. }
  134. sum[j]+=;//回溯
  135. }
  136. sum[i]+=;//回溯
  137. }
  138. }
  139. //把剩下的牌出完
  140. for(int i=;i<=;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=<<;//搞大一点
  155. int x,y;
  156. memset(sum,,sizeof sum);//多次询问,记得清零
  157. for(int i=;i<=n;i++)
  158. {
  159. scanf("%d%d",&x,&y);
  160. if (x==)
  161. {
  162. sum[]++;//把两张王存在一起(但是带牌的时候注意不要做对儿)
  163. }
  164. else
  165. if(x==)
  166. {
  167. sum[]++;//由于A的牌值大所以往后放
  168. }
  169. else sum[x]++;//其他牌存在相应位置
  170. }
  171. dfs();//开始暴搜
  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中发现了还有用贪心的(还是对的!!)

  • 同样还是贪心打散牌,这也是不超时的原因。对于上面的贪心就会存在一些问题,增强后要考虑拆牌,王,也算是单排,可以当对子出。
  • 拆牌(重点): 什么时候可以拆呢?
  1. 被四带的时候不能拆,这样可能会多出一次。
  2. 在单牌和对牌很多时,三张和炸弹不可以拆,会多打,可能多很多次。
  3. 反过来,单牌和对牌不是很多时,不就可以拆了吗?当单牌和对牌比三张和炸弹少时,就可以拆了。。。
  4. 证明(没什么用):这时不拆会没得带,例如单牌只能单出,如果把三张拆成一单一对,让其余和炸弹一起走,就会少一步
  5. 举个栗子:  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步

四张是一样的。

愉快的代码环节(因为尝试新的打法,所以和原题的打法不太一样)

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. using namespace std;
  5. int n,t,ans;
  6. int pai[];//存牌
  7. int san_pai();
  8. void feiji(int step);
  9. void shunzi(int step);
  10. void liandui(int step);
  11. void chupai(int step) { //开始出牌
  12. if(step>=ans)
  13. return ; //最优性剪枝
  14. int tmp=san_pai(); //打散牌
  15. ans=min(tmp+step,ans); //更新最优解
  16. feiji(step); //飞机
  17. shunzi(step); //顺子
  18. liandui(step);//连对
  19. }
  20. void feiji(int step)//出飞机
  21. {
  22. int l,end;
  23. for(int st=;st<=;++st) //枚举连续牌起始点
  24. {
  25. l=;
  26. while(pai[st+l]>=)
  27. l++;//找出最大长度
  28. for(int j=l;j>=;--j)
  29. {//枚举出牌长度
  30. end=st+j-;
  31. for(int k=st;k<=end;k++)
  32. pai[k]-=;//出飞机
  33. chupai(step+);//继续出牌
  34. for(int k=st;k<=end;k++)
  35. pai[k]+=;//搜索回溯
  36. }
  37. }
  38. return;
  39. }
  40. void liandui(int step)
  41. {//连对
  42. int l,end;
  43. for(int st=;st<=;++st)
  44. {//枚举连续牌起始点
  45. l=;
  46. while(pai[st+l]>=)l++;//找出最大长度
  47. for(int j=l;j>=;--j) {//枚举出牌长度
  48. end=st+j-;
  49. for(int k=st;k<=end;k++)
  50. pai[k]-=;//出连对
  51. chupai(step+);//继续出牌
  52. for(int k=st;k<=end;k++)
  53. pai[k]+=;//搜索回溯
  54. }
  55. }
  56. return;
  57. }
  58. void shunzi(int step) //顺子
  59. {
  60. int l,end;
  61. for(int st=;st<=;++st)
  62. {//枚举连续牌起始点
  63. l=;
  64. while(pai[st+l]>=)
  65. l++;//找出最大长度
  66. for(int j=l;j>=;--j)
  67. {
  68. end=st+j-;
  69. for(int k=st;k<=end;k++)
  70. pai[k]-=;//出顺子
  71. chupai(step+);//继续出牌
  72. for(int k=st;k<=end;k++)
  73. pai[k]+=;//搜索回溯
  74. }
  75. }
  76. return;
  77. }
  78. int san_pai() {//贪心打散牌
  79. int zs[],num=;
  80. memset(zs,,sizeof(zs));
  81. bool wangzha=false;
  82. if(pai[]==)
  83. wangzha=true;//是否有王炸
  84. zs[]+=pai[]; //王算单牌
  85. for(int i=;i<=;++i)zs[pai[i]]++;//统计个数
  86. /****** 暴力出奇迹 , N!过样例 ******/
  87. while(!zs[]&&zs[]==&&zs[]==&&zs[]>)
  88. zs[]-=,zs[]--,zs[]--,num+=;//神特判
  89. //把一个炸拆成3张和单牌,再出一组四带二单和三带一
  90. while(!zs[]&&zs[]==&&zs[]==&&zs[]>)
  91. zs[]-=,zs[]--,zs[]--,num+=;//神特判
  92. //把一组三张拆成一对和一单,再出一组四带二单和三带二
  93. if(zs[]+zs[]>zs[]+zs[])//三四张的比单牌和对牌多,拆着打
  94. while(zs[]&&zs[]&&zs[])
  95. zs[]--,zs[]--,zs[]++,zs[]--,num++;//拆三张,4带两对余一单
  96. if(zs[]+zs[]>zs[]+zs[])//还多继续拆
  97. while(zs[]&&zs[]&&zs[])
  98. zs[]--,zs[]--,zs[]++,zs[]--,num++;//拆三张,4带两单余一对
  99. while(zs[]&&zs[]>)
  100. zs[]--,zs[]-=,num++;//四带两单
  101. while(zs[]&&zs[]>)
  102. zs[]--,zs[]-=,num++;//四带两对
  103. while(zs[]&&zs[] )
  104. zs[]-- ,zs[]--,num++;//对看成两单再四带
  105. if(zs[]%==&&zs[]+zs[]<=) //三张的太多了拆三张
  106. while(zs[]>)
  107. zs[]-=,num+=;//把一组三张拆成单和对,再出三带一和三带二
  108. while(zs[]&&zs[] )
  109. zs[]-- ,zs[]--,num++;//三带一
  110. while(zs[]&&zs[] )
  111. zs[]-- ,zs[]--,num++;//三带二
  112. //还剩三张和炸,组合出
  113. while(zs[]>&&zs[])
  114. zs[]--,zs[]-=,num+=;//把一个炸拆成一对和两单,再出三带二和四带两单
  115. while(zs[]>&&zs[])
  116. zs[]--,zs[]-=,num+=;//把一个炸拆成两对,再出两组三带一对
  117. while(zs[]>)
  118. zs[]-=,num+=; //同上,把一组三张拆成单和对,再出三带一和三带二
  119. while(zs[]>)zs[]-=,num++; //把一个炸拆成两对,再出一组四带两对
  120. if(wangzha&&zs[]>=)//有王炸并且没被带跑
  121. return num+zs[]+zs[]+zs[]+zs[]-;//双王一块出
  122. else
  123. return num+zs[]+zs[]+zs[]+zs[];//出剩余的牌,返回答案
  124. }
  125. int main()
  126. {
  127. cin>>t>>n;
  128. int a,b;
  129. while(t--)
  130. {
  131. ans=<<;
  132. memset(pai,,sizeof(pai));
  133. for(int i=; i<=n; ++i)
  134. {
  135. cin>>a>>b;
  136. if(a==)
  137. pai[]++; //14代表A
  138. else
  139. if(a==)
  140. {
  141. pai[]++;//1代表王
  142. }
  143. else
  144. pai[a]++;
  145. }
  146. chupai();
  147. printf("%d\n",ans);
  148. }
  149. }

最后预祝BK201    AK   IOI,还有希望shymm早日攒够350块钱,可以随时去这里水一下

NOIP原题 斗地主(20190804)的更多相关文章

  1. NOIP原题板刷

    update 10.11 我可能已经刷完大部分了,可是这篇blog我也不想更了 这个人很懒,做了很多题但是不想写题解,也不想更blog,所以这篇blog又咕咕了. 把从 \(1997-2017\) 近 ...

  2. 【做题笔记】[NOIOJ,非NOIp原题]装箱问题

    题意:给定一些矩形,面积分别是 \(1\times 1,2\times 2,3\times 3,4\times 4,5\times 5,6\times 6\).您现在知道了这些矩形的个数 \(a,b, ...

  3. NOIP2016原题终结测试(2017081801)

    NOIP2016还有几道原题没有写掉,今天就一并布置掉. 答案的问题,有部分会先放到NOIP题解中,是单独发布的. 最后会汇总放在答案中,各位不要急. 还有,后期会有原创题测试,这个不急,反正11月才 ...

  4. noip做题记录+挑战一句话题解?

    因为灵巧实在太弱辽不得不做点noip续下命QQAQQQ 2018 积木大赛/铺设道路 傻逼原题? 然后傻逼的我居然检查了半天是不是有陷阱最后花了差不多一个小时才做掉我做过的原题...真的傻逼了我:( ...

  5. Noip前的大抱佛脚----Noip真题复习

    Noip前的大抱佛脚----Noip真题复习 Tags: Noip前的大抱佛脚 Noip2010 题目不难,但是三个半小时的话要写四道题还是需要码力,不过按照现在的实力应该不出意外可以AK的. 机器翻 ...

  6. 历年NOIP真题总结

    前言:最近把历年的NOIP真题肝了一遍(还有3个紫题先咕掉了),主要是到1998年的提高组的题.把题目的做题简要思路搁在这儿,一个是为了考前翻一翻,想想自己的哪些思路要梳理的什么什么的,反正怎么说呢, ...

  7. NOIP 模拟题

    目录 T1 : grid T2 : ling T3 : threebody 数据可私信我. T1 : grid 题目:在一个\(n*n\)的方格中,你只能斜着走.为了让问题更简单,你还有一次上下左右走 ...

  8. NOIP真题索引

    NOIP真题索引 NOIP2019 Day 1 格雷码 括号树 树上的数 Day 2 Emiya 家今天的饭 划分 树的重心 NOIP2018 Day 1 铺设道路 货币系统 赛道修建 Day 2 旅 ...

  9. [CF676C]Vasya and String(尺取法,原题)

    题目链接:http://codeforces.com/contest/676/problem/C 原题题解链接:http://www.cnblogs.com/vincentX/p/5405468.ht ...

随机推荐

  1. 01 python安装与初识

    一.简要概述 python学习时设计要大于开发. 二.编程语言 1.分类 编程语言分为高级语言和低级语言.高级语言如python.c#,Java.PHP等,低级语言(基础语言)如C.汇编语言. 2.机 ...

  2. Spring 源码阅读 二

    程序入口: 接着上一篇博客中看完了在AnnotationConfigApplicationContext的构造函数中的register(annotatedClasses);将我们传递进来的主配置类添加 ...

  3. Vue入门教程 第二篇 (数据绑定与响应式)

    数据绑定 Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统: <div id="app"> {{ message }} </ ...

  4. bugku web8

    打开网站,是一段PHP代码, <?php extract($_GET); if (!empty($ac)) { $f = trim(file_get_contents($fn)); if ($a ...

  5. Vue学习系列(二)——组件详解

    前言 在上一篇初识Vue核心中,我们已经熟悉了vue的两大核心,理解了Vue的构建方式,通过基本的指令控制DOM,实现提高应用开发效率和可维护性.而这一篇呢,将对Vue视图组件的核心概念进行详细说明. ...

  6. 算法学习之剑指offer(三)

    题目1 题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 如果一个整数不为0,那么这个整数至少有一位是1.如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在 ...

  7. linux系统定时发送邮件

    Linux Centos7系统下利用自带的mail发送邮件服务 简介 本章分为五部分. 第一部分是基于虚拟机下的CentOS 7环境定时发送邮件: 第二部分是基于在阿里云购买的CentOS 7服务器环 ...

  8. java位运算,逻辑运算符

    位运算逻辑运算符包括: 与(&),非(~),或(|),异或(^). &:  条件1&条件2  ,只有条件1和条件2都满足, 整个表达式才为真true,  只要有1个为false ...

  9. 解决vue组件内前置路由守卫beforeRouteEnter无法获取上下文this

    问题描述 vue框架,只有在报名页面报名成功,然后自动跳转到订单详情,才弹出一个引流弹窗,其他情况均不弹出,我就想到使用vue 的组件内前置守卫beforeRouteEnter来实现.beforeRo ...

  10. [专题练习] Part1 搜索

    本文中的链接有的是题解有的是题目链接,已经搞混了... 一.DFS(深度优先搜索) 过于水略过. 二.BFS(广度优先搜索) 同上. 三.记忆化 记忆化搜索,就是我们的状态会重复利用,为了防止状态的重 ...