题目描述

牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的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
8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1

输出 #1
  3

输入 #2
    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

输出 #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[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)
 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)
 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)
 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)
 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)
109                 continue;//自己不能带自己喽
110                 sum[j]--;//出被带的第一张单张牌
111                 for (int k=2;k<=15;k++)//找第二张单张
112                 {
113                     if(sum[k]<=0)
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)
124                 continue;
125                 sum[j]-=2;//出被带的第一对牌
126                 for(int k=2;k<=14;k++)
127                 {
128                     if(sum[k]<=1)
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中发现了还有用贪心的(还是对的!!)

  • 同样还是贪心打散牌,这也是不超时的原因。对于上面的贪心就会存在一些问题,增强后要考虑拆牌,王,也算是单排,可以当对子出。
  • 拆牌(重点): 什么时候可以拆呢?
  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[20];//存牌
  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=3;st<=13;++st) //枚举连续牌起始点
 24     {
 25         l=0;
 26         while(pai[st+l]>=3)
 27         l++;//找出最大长度
 28         for(int j=l;j>=2;--j)
 29         {//枚举出牌长度
 30             end=st+j-1;
 31             for(int k=st;k<=end;k++)
 32             pai[k]-=3;//出飞机
 33             chupai(step+1);//继续出牌
 34             for(int k=st;k<=end;k++)
 35             pai[k]+=3;//搜索回溯
 36         }
 37     }
 38     return;
 39 }
 40 void liandui(int step)
 41 {//连对
 42     int l,end;
 43     for(int st=3;st<=12;++st)
 44     {//枚举连续牌起始点
 45         l=0;
 46         while(pai[st+l]>=2)l++;//找出最大长度
 47         for(int j=l;j>=3;--j) {//枚举出牌长度
 48             end=st+j-1;
 49             for(int k=st;k<=end;k++)
 50             pai[k]-=2;//出连对
 51             chupai(step+1);//继续出牌
 52             for(int k=st;k<=end;k++)
 53             pai[k]+=2;//搜索回溯
 54         }
 55     }
 56     return;
 57 }
 58 void shunzi(int step) //顺子
 59 {
 60     int l,end;
 61     for(int st=3;st<=10;++st)
 62     {//枚举连续牌起始点
 63         l=0;
 64         while(pai[st+l]>=1)
 65         l++;//找出最大长度
 66         for(int j=l;j>=5;--j)
 67         {
 68             end=st+j-1;
 69             for(int k=st;k<=end;k++)
 70             pai[k]-=1;//出顺子
 71             chupai(step+1);//继续出牌
 72             for(int k=st;k<=end;k++)
 73             pai[k]+=1;//搜索回溯
 74         }
 75     }
 76     return;
 77 }
 78 int san_pai() {//贪心打散牌
 79     int zs[5],num=0;
 80     memset(zs,0,sizeof(zs));
 81     bool wangzha=false;
 82     if(pai[1]==2)
 83     wangzha=true;//是否有王炸
 84     zs[1]+=pai[1];            //王算单牌
 85     for(int i=2;i<=14;++i)zs[pai[i]]++;//统计个数
 86     /******  暴力出奇迹 , N!过样例  ******/
 87     while(!zs[3]&&zs[1]==1&&zs[2]==1&&zs[4]>1)
 88     zs[4]-=2,zs[1]--,zs[2]--,num+=2;//神特判
 89     //把一个炸拆成3张和单牌,再出一组四带二单和三带一
 90     while(!zs[2]&&zs[1]==1&&zs[4]==1&&zs[3]>1)
 91     zs[3]-=2,zs[1]--,zs[4]--,num+=2;//神特判
 92     //把一组三张拆成一对和一单,再出一组四带二单和三带二
 93     if(zs[3]+zs[4]>zs[1]+zs[2])//三四张的比单牌和对牌多,拆着打
 94         while(zs[4]&&zs[2]&&zs[3])
 95         zs[2]--,zs[3]--,zs[1]++,zs[4]--,num++;//拆三张,4带两对余一单
 96     if(zs[3]+zs[4]>zs[1]+zs[2])//还多继续拆
 97         while(zs[4]&&zs[1]&&zs[3])
 98         zs[1]--,zs[3]--,zs[2]++,zs[4]--,num++;//拆三张,4带两单余一对
 99     while(zs[4]&&zs[1]>1)
100     zs[4]--,zs[1]-=2,num++;//四带两单
101     while(zs[4]&&zs[2]>1)
102     zs[4]--,zs[2]-=2,num++;//四带两对
103     while(zs[4]&&zs[2]  )
104     zs[4]-- ,zs[2]--,num++;//对看成两单再四带
105     if(zs[3]%3==0&&zs[1]+zs[2]<=1)                //三张的太多了拆三张
106         while(zs[3]>2)
107         zs[3]-=3,num+=2;//把一组三张拆成单和对,再出三带一和三带二
108     while(zs[3]&&zs[1]  )
109     zs[3]-- ,zs[1]--,num++;//三带一
110     while(zs[3]&&zs[2]  )
111     zs[3]-- ,zs[2]--,num++;//三带二
112     //还剩三张和炸,组合出
113     while(zs[4]>1&&zs[3])
114     zs[3]--,zs[4]-=2,num+=2;//把一个炸拆成一对和两单,再出三带二和四带两单
115     while(zs[3]>1&&zs[4])
116     zs[4]--,zs[3]-=2,num+=2;//把一个炸拆成两对,再出两组三带一对
117     while(zs[3]>2)
118     zs[3]-=3,num+=2;                //同上,把一组三张拆成单和对,再出三带一和三带二
119     while(zs[4]>1)zs[4]-=2,num++;                //把一个炸拆成两对,再出一组四带两对
120     if(wangzha&&zs[1]>=2)//有王炸并且没被带跑
121         return num+zs[2]+zs[3]+zs[4]+zs[1]-1;//双王一块出
122     else
123         return num+zs[1]+zs[2]+zs[3]+zs[4];//出剩余的牌,返回答案
124 }
125 int main()
126 {
127     cin>>t>>n;
128     int a,b;
129     while(t--)
130     {
131         ans=1<<30;
132         memset(pai,0,sizeof(pai));
133         for(int i=1; i<=n; ++i)
134         {
135             cin>>a>>b;
136             if(a==1)
137             pai[14]++;    //14代表A
138             else
139             if(a==0)
140             {
141                 pai[1]++;//1代表王
142             }
143             else
144             pai[a]++;
145         }
146         chupai(0);
147         printf("%d\n",ans);
148     }
149 }

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

转载于:https://www.cnblogs.com/2529102757ab/p/11298520.html

NOIP原题 斗地主(20190804)相关推荐

  1. mysql 1z0_MySQL 8 OCP(1Z0-908)认证考试题库原题(第10题)

    Mysql 8.0 OCP认证考试原题题库整理(CUUG内部资料)-第10题 Choose the best answer. Examine these commands, which execute ...

  2. mysql ocp 认证 题库_MySQL 8 OCP(1Z0-908)认证考试题库原题(第10题)

    Mysql 8.0 OCP认证考试原题题库整理(CUUG内部资料)-第10题 Choose the best answer. Examine these commands, which execute ...

  3. 网页设计上机考试原题_计算机二级考试即将到达战场,各单位准备!!!!

    计算机二级 今年的特殊情况 导致有些考试 推迟了半年之久 如今二级考试在即 你复习的怎么样了呢? 很多同学往往考前几天才想起来 "呀,我好像有个二级考试要考了" 而此时的心路历程往 ...

  4. 网页设计上机考试原题_全国计算机三级信息安全考试 经验分享

    为什么选择信息安全?误打误撞报的名,正好又听说是三级里面最好过的. 1)时间: 九月份的考试,大概六月份报名. 我在9月2日开学后,才开始准备的,也就二十几天的时间,而且我白天课不少,基本只有晚上和双 ...

  5. shl性格测试_德勤2021秋招网申Tips+SHL笔试原题

    德勤校招时间表及对应的提醒 在线网申+线上笔试 2020年7月31日-2020年10月10日 "路人"说:一定一定一定要安排好网申时间--完成网申,即刻收到笔试,同时在收到笔试的5 ...

  6. 云计算架构师分享:容器云在金融企业的落地方案 | 周末送资料(原题:某保险公司容器云PaaS平台建设实践经验分享)

    [摘要]随着技术和社区的成熟,容器.Kubernetes.微服务等新事物不再只是概念,已在很多企业落地并发挥了生产力,对容器和PaaS的需求也从试探性转向规模化推广和纵深探索,建设企业级容器PaaS平 ...

  7. 闵梓轩大佬のnoip模拟题D1 总结 2017/10/26

    背景 题目概括 T1 题面 分析 90分算法 满分算法 T2 题面 分析 部分分算法 满分算法 满分代码 T3 题面 分析 代码 总结 背景 这道题目是去年的金牌大佬闵梓轩在一年前出的一套noip模拟 ...

  8. 第十四届蓝桥杯第三期模拟赛 C/C++ B组 原题与详解

    本篇文章对第十四届蓝桥杯第三期模拟赛所有的题目进行了详细解析.如果大家还想刷题的话,我给大家整理出了第十二届的省赛真题:第十二届省赛C/C++ B组真题.推荐大家可以去做一下. 文章目录 一.填空题 ...

  9. 【noip模拟题】天神下凡(贪心)

    vijos某次模拟赛原题... 处理出每个圆的一级祖先就行了... 其实没有那么麻烦,贪心即可出解. 我们将每个圆转换成线段后按左端点小右端点大的方法排序 然后维护一个栈: 对于每一个圆i 如果栈顶右 ...

  10. 2019河南省第十二届ACM省赛原题题目及省赛榜单

    题目 榜单 Rank Name Solved Time     A     B     C     D     E     F     G     H     I     J Total att/so ...

最新文章

  1. 一个NullPointerException,竟然有这么多花样!
  2. doe全称是什么意思_BVV线和RVV都是护套线,二者有什么区别?【辽宁津达线缆】...
  3. 如何做到微信机器人不封号_微信如何做到一键群发所有群
  4. ASP.NET MVC 5 使用autofac实现DI
  5. Java设计模式透析之 —— 策略(Strategy)
  6. echarts画布_vue中动态设置echarts画布大小
  7. Python基础教程:xrange和range的使用区别
  8. java 时间戳和PHP时间戳 的转换 php time()
  9. mac或者linux磁力下载方法:远离渣雷
  10. 【Php】最最简单的php环境搭建
  11. vlan为什么能隔离广播域_路由交换技术-VLAN原理及配置
  12. VC实现对话框上信息的显示
  13. xtrabackup备份还原MySQL数据库
  14. 如何快速制作脚本,不用插件也能用按键精灵后台发送消息
  15. 云杰恒指:6.20恒指期货早盘资讯
  16. iOS之Category和Extention的区别
  17. 设置表头QHeaderView
  18. 台式电脑打不开计算机c盘,电脑打不开显示C盘损坏怎么办
  19. (NeurIPS 2019) Learning Object Bounding Boxes for 3D Instance Segmentation on Point Clouds
  20. m2e-wtp的作用

热门文章

  1. 万年历黄历星座查询v3.6.9引流吸粉 实用工具 流量变现小程序
  2. c语言爱心代码我爱你,C语言告白代码,一闪一闪亮晶晶~
  3. 微信内网页分享,分享者能看到分享的图片(描述),但被分享者无法看到
  4. piranha启动报错
  5. SQL Server2008R2中文版安装教程
  6. 乐优商城_第5章_-vue入门
  7. JS快速获取本周、本月时间区间的方法
  8. ONF代理执行董事Rick Bauer出走MEF
  9. 关于MVVM的面试问题
  10. Bootstrap关于导航条点击后移出此区域背景颜色变白