题目描述

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

     下面是搜索顺序:

  • 要注意一个地方两个王不一样,不能当一对,只有火箭可以。,然后就是暴搜,感觉就要注意一下细节。
 #include<iostream>
using namespace std;
int T,n,ans,sum[];
void dfs(int x)//x为出牌次数
{
if (x>=ans)
return;
//顺子
int k=;//单顺子
for (int i=;i<=;i++)//注意2和大小王不能考虑
{
if(sum[i]==) k=;//顺子断了
else
{
k++;//顺子长度增加
if(k>=)//单顺子达到五张
{
for(int j=i;j>=i-k+;j--)
sum[j]--;//出牌
dfs(x+);//继续搜
for(int j=i;j>=i-k+;j--)
sum[j]++;//回溯
}
}
}
k=;//双顺子
for(int i=;i<=;i++)
{
if(sum[i]<=) k=;
else
{
k++;
if(k>=)//双顺子达到三组
{
for(int j=i;j>=i-k+;j--)
sum[j]-=;//出牌
dfs(x+);
for(int j=i;j>=i-k+;j--)
sum[j]+=;//回溯
}
}
}
k=;//三顺子 //以下同理
for(int i=;i<=;i++)
{
if(sum[i]<=) k=;
else
{
k++;
if(k>=)//三顺子达到两组
{
for(int j=i;j>=i-k+;j--) sum[j]-=;
dfs(x+);
for(int j=i;j>=i-k+;j--) sum[j]+=;
}
}
}
//带牌
for(int i=;i<=;i++)//枚举有3张或4张的牌(这样才能带牌)
{
if(sum[i]<=)
{
if(sum[i]<=)
continue;//三张以下(不含三张)不能带牌
sum[i]-=;//出掉用来带别人的牌
for(int j=;j<=;j++)//带单张
{
if(sum[j]<=)
continue;//没有牌怎么带??
sum[j]--;//出掉被带的单张
dfs(x+);
sum[j]++;//回溯
}
for(int j=;j<=;j++)//带一对
{
if(sum[j]<=)
continue;//没有一对怎么带?
sum[j]-=;//出掉被带的一对
dfs(x+);
sum[j]+=;//回溯
}
sum[i]+=;//回溯
}
else//大于3可以4带别的也可以3带别的
{
sum[i]-=;//先用3张带别的
for(int j=;j<=;j++) //带单张 //以下原理同上
{
if(sum[j]<=)
continue;
sum[j]--;
dfs(x+);
sum[j]++;
}
for(int j=;j<=;j++) //带一对
{
if(sum[j]<=)
continue;
sum[j]-=;
dfs(x+);
sum[j]+=;
}
sum[i]+=; sum[i]-=; //再用4张带别的
for(int j=;j<=;j++) //带2个单张
{
if(sum[j]<=)
continue;//自己不能带自己喽
sum[j]--;//出被带的第一张单张牌
for (int k=;k<=;k++)//找第二张单张
{
if(sum[k]<=)
continue;
sum[k]--;//出被带的第二张单张牌
dfs(x+);
sum[k]++;//回溯
}
sum[j]++;//回溯
}
for(int j=;j<=;j++)//带2个对儿
{
if(sum[j]<=)
continue;
sum[j]-=;//出被带的第一对牌
for(int k=;k<=;k++)
{
if(sum[k]<=)
continue;
sum[k]-=;//出被带的第二对牌
dfs(x+);
sum[k]+=;//回溯
}
sum[j]+=;//回溯
}
sum[i]+=;//回溯
}
}
//把剩下的牌出完
for(int i=;i<=;i++)
{
if(sum[i])
{
x++;
}
}
ans=min(ans,x);
}
int main()
{
scanf("%d%d",&T,&n);
while(T--)
{
ans=<<;//搞大一点
int x,y;
memset(sum,,sizeof sum);//多次询问,记得清零
for(int i=;i<=n;i++)
{
scanf("%d%d",&x,&y);
if (x==)
{
sum[]++;//把两张王存在一起(但是带牌的时候注意不要做对儿)
}
else
if(x==)
{
sum[]++;//由于A的牌值大所以往后放
}
else sum[x]++;//其他牌存在相应位置
}
dfs();//开始暴搜
printf("%d\n",ans);
}
}

然后就可以把这道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步

四张是一样的。

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

 #include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,t,ans;
int pai[];//存牌
int san_pai();
void feiji(int step);
void shunzi(int step);
void liandui(int step);
void chupai(int step) { //开始出牌
if(step>=ans)
return ; //最优性剪枝
int tmp=san_pai(); //打散牌
ans=min(tmp+step,ans); //更新最优解
feiji(step); //飞机
shunzi(step); //顺子
liandui(step);//连对
}
void feiji(int step)//出飞机
{
int l,end;
for(int st=;st<=;++st) //枚举连续牌起始点
{
l=;
while(pai[st+l]>=)
l++;//找出最大长度
for(int j=l;j>=;--j)
{//枚举出牌长度
end=st+j-;
for(int k=st;k<=end;k++)
pai[k]-=;//出飞机
chupai(step+);//继续出牌
for(int k=st;k<=end;k++)
pai[k]+=;//搜索回溯
}
}
return;
}
void liandui(int step)
{//连对
int l,end;
for(int st=;st<=;++st)
{//枚举连续牌起始点
l=;
while(pai[st+l]>=)l++;//找出最大长度
for(int j=l;j>=;--j) {//枚举出牌长度
end=st+j-;
for(int k=st;k<=end;k++)
pai[k]-=;//出连对
chupai(step+);//继续出牌
for(int k=st;k<=end;k++)
pai[k]+=;//搜索回溯
}
}
return;
}
void shunzi(int step) //顺子
{
int l,end;
for(int st=;st<=;++st)
{//枚举连续牌起始点
l=;
while(pai[st+l]>=)
l++;//找出最大长度
for(int j=l;j>=;--j)
{
end=st+j-;
for(int k=st;k<=end;k++)
pai[k]-=;//出顺子
chupai(step+);//继续出牌
for(int k=st;k<=end;k++)
pai[k]+=;//搜索回溯
}
}
return;
}
int san_pai() {//贪心打散牌
int zs[],num=;
memset(zs,,sizeof(zs));
bool wangzha=false;
if(pai[]==)
wangzha=true;//是否有王炸
zs[]+=pai[]; //王算单牌
for(int i=;i<=;++i)zs[pai[i]]++;//统计个数
/****** 暴力出奇迹 , N!过样例 ******/
while(!zs[]&&zs[]==&&zs[]==&&zs[]>)
zs[]-=,zs[]--,zs[]--,num+=;//神特判
//把一个炸拆成3张和单牌,再出一组四带二单和三带一
while(!zs[]&&zs[]==&&zs[]==&&zs[]>)
zs[]-=,zs[]--,zs[]--,num+=;//神特判
//把一组三张拆成一对和一单,再出一组四带二单和三带二
if(zs[]+zs[]>zs[]+zs[])//三四张的比单牌和对牌多,拆着打
while(zs[]&&zs[]&&zs[])
zs[]--,zs[]--,zs[]++,zs[]--,num++;//拆三张,4带两对余一单
if(zs[]+zs[]>zs[]+zs[])//还多继续拆
while(zs[]&&zs[]&&zs[])
zs[]--,zs[]--,zs[]++,zs[]--,num++;//拆三张,4带两单余一对
while(zs[]&&zs[]>)
zs[]--,zs[]-=,num++;//四带两单
while(zs[]&&zs[]>)
zs[]--,zs[]-=,num++;//四带两对
while(zs[]&&zs[] )
zs[]-- ,zs[]--,num++;//对看成两单再四带
if(zs[]%==&&zs[]+zs[]<=) //三张的太多了拆三张
while(zs[]>)
zs[]-=,num+=;//把一组三张拆成单和对,再出三带一和三带二
while(zs[]&&zs[] )
zs[]-- ,zs[]--,num++;//三带一
while(zs[]&&zs[] )
zs[]-- ,zs[]--,num++;//三带二
//还剩三张和炸,组合出
while(zs[]>&&zs[])
zs[]--,zs[]-=,num+=;//把一个炸拆成一对和两单,再出三带二和四带两单
while(zs[]>&&zs[])
zs[]--,zs[]-=,num+=;//把一个炸拆成两对,再出两组三带一对
while(zs[]>)
zs[]-=,num+=; //同上,把一组三张拆成单和对,再出三带一和三带二
while(zs[]>)zs[]-=,num++; //把一个炸拆成两对,再出一组四带两对
if(wangzha&&zs[]>=)//有王炸并且没被带跑
return num+zs[]+zs[]+zs[]+zs[]-;//双王一块出
else
return num+zs[]+zs[]+zs[]+zs[];//出剩余的牌,返回答案
}
int main()
{
cin>>t>>n;
int a,b;
while(t--)
{
ans=<<;
memset(pai,,sizeof(pai));
for(int i=; i<=n; ++i)
{
cin>>a>>b;
if(a==)
pai[]++; //14代表A
else
if(a==)
{
pai[]++;//1代表王
}
else
pai[a]++;
}
chupai();
printf("%d\n",ans);
}
}

最后预祝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. Java如何安装JDK,配置环境变量。超级详细图及操作

    突然想起自己大学刚接触java的时候,要下载JDK和配置环境变量,那时候我上网找了很多教学,结果发现很多的博主都是表达不太清晰,或者是我理解能力差点,导致我那时候搞了一个多小时才搞定,而且事后每次我重 ...

  2. jquery经常用到的代码段

    1.1jquery实现手风琴效果 <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"&g ...

  3. Python3程序设计指南:01 过程型程序设计快速入门

    大家好,从本文开始将逐渐更新Python教程指南系列,为什么叫指南呢?因为本系列是参考<Python3程序设计指南>,也是作者的学习笔记,希望与读者共同学习. .py文件中的每个估计都是顺 ...

  4. 编程杂谈——使用emplace_back取代push_back

    近日在YouTube视频上看到关于vector中emplace_back与push_back区别的介绍,深感自己在现代C++中还是有不少遗漏的知识点,遂写了段代码,尝试比较两者的差别. 示例代码 #i ...

  5. Coins POJ - 1742

    给出硬币面额及每种硬币的个数,求从1到m能凑出面额的个数. Input 多组数据,每组数据前两个数字为n,m.n表示硬币种类数,m为最大面额,之后前n个数为每种硬币的面额,后n个数为相应每种硬币的个数 ...

  6. 虚拟机上安装centos8.0

    一.准备宿主机 为了培训Hadoop生态的部署和调优技术,需要准备3台虚拟机部署Hadoop集群环境,能够保证HA,即主要服务没有单点故障,可执行基本功能,完成小内存模式的参数调整. 1.1.准备安装 ...

  7. 编译 lame for iOS

    网上找了许多编译lame的教程,结果都是编译失败,多次尝试后发现是编译脚本放错路径了,记录下编译的过程,把编译脚本放到源码文件夹中和修改编译脚本中的目录是关键: 一.首先去Lame官网 http:// ...

  8. Python3 pygal 与 pygal_maps_world 绘制世界地图

    直接代码: import pygalfrom pygal_maps_world.i18n import COUNTRIES def word_country_map(): ""&q ...

  9. SQL 存储过程示例讲解

    create proc score_result ) --参数 as declare --定义变量@courseNo int, @testTime1 datetime, @avg int begin ...

  10. [CF431C]k-Tree

    题目描述 Quite recently a creative student Lesha had a lecture on trees. After the lecture Lesha was ins ...