HDOJ4431Mahjong 模拟
第一次这么专心的这么坚持的开始写这种模拟题,从昨晚的构造思路到300行代码,今天的重写,再到构造了一个特殊情况Hack了杭电的数据,简直完美简直Nice!
题目链接:HDOJ4431
先说说题意:国粹!不按题目描述来解释了,按照麻将官方说法来解释
介绍牌型:4种类型
1万到9万:用1m到9m
1条到9条:用1s到9s
1筒到9筒:用1p到9p
花牌:东西南北中发白:用1c到7c
介绍胡牌方式:
(1)七小对:一对牌就是两个一样的牌。手上13张牌+听的牌(题目中要求的),构成7个对子就可以胡牌。注意这儿有个Hack点!(杭电没有这种数据)7对牌不一定不同哦!比如4个1万可以看作是两对
(2)十三幺:1万9万1条9条1筒9筒东西南北中发白,这13张牌每张1张,然后第14张在这13张中再选任意一张即可
(3)普通胡牌方式:1对+4句话
对于1对,与七小对的解释中对子一样
对于1句话,又可以分成两种情况
第一种,ABC形式。即花色相同的,连续的3个数字的不是花牌的牌型。如1万2万3万(注意,题目中强调了不能是花牌,即东西南1c2c3c不算做一句话)
第二种,AAA形式。即三张一模一样的牌,即:东东东,1万1万1万
对于手中的13张+听的这张,构成任意的1对+4句话的形式,就可以胡牌
昨晚的思路分析:
首先,模拟题得把思路梳理好:
构造数据结构(如何存储牌,1s,1m怎么表示,题目中强调了输出牌的格式)------枚举每一张牌(注意牌的上限张数是4张)------如何判断7小对------如何判断十三幺------找到任意对子+4句话(在这里,每张牌可能组成的一句话的方式不同,如1万1万1万2万2万3万3万有两个理解,3个1万看作一句话,就可以听2万和3万,把1万2万3万看作一句话,就是听1万,把1对1万当作对子,1万2万3万当作一句话,听1万和4万,所以这种牌听1万2万3万4万)
同样的分析方法:1万1万1万2万3万4万5万6万7万8万9万9万9万:是听所有万子的!(清一色的九子连环)
如何给定标记和排序输出
一开始的代码风格是各种瞎弄,for循环之类的写一堆,然后一直tle
错误代码(答案也不全对,但是跟着思路很清晰):wrong
如果看到这个思路,可以更新到其他的想法,就建议不要再往下看了,自己试着写写,所有的坑点基本已经列出
睡醒之后,跟着这个思路设计了不同的数据结构和枚举策略
1.枚举每一张牌的时候,就用其int值与牌一一对应,按照题目描述的顺序依次编号,1万到9万是1到9号,符号标记为0,枚举的时候不用来回转换可以省时间
2.判断的时候,不是用14张牌是否放到对子和4句话中作为判断,而是用num数组记录每种牌出现了几次
num数组有几个好处,一个深搜的时候好标记也好撤回,用了那几张牌很清楚;另外一个就避免了第一个wrong代码的排序问题,从1到34排序已经是排好的
再一个,用num数组判断七小对和十三幺就是简单的if语句就好;最后一个就是枚举很方便,需要哪张牌就num【x】++,最后再num【x】--就恢复了原手牌;
3.4句话的特殊判断要记得,东西南北中发白是没有ABC的形式只有AAA的形式的,另外再深搜的时候记得提前剪枝ruturn回来避免超时,而且记得恢复num数组
其他的细节代码中都给出注释
从昨晚的写了三个多小时的TLE到今天一个小时就能AC
模拟真的是需要清晰的思路和高超的编辑技术的,加油!
#include<map>
#include<set>
#include<math.h>
#include<time.h>
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<stdio.h>
#include<cstring>
#include<string.h>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define ll rt<<1
#define rr rt<<1|1
#define LL long long
#define ULL unsigned long long
#define maxn 1000000
#define eps 1e-6
#define input freopen("input.txt","r",stdin)
#define output freopen("output.txt","w",stdout)
int t;
struct node{
int num,ch;//ch==0:m//ch==1:s//ch==2:p//ch==3:c
int all;
}a[20],ans[50];
char s[5];
int num[50],tot,flag;
char getch(int x){
//根据一一对应算出花色
if (x>=1&&x<=9) return 'm';
if (x>=10&&x<=18) return 's';
if (x>=19&&x<=27) return 'p';
return 'c';
}
int getnum(int x){
//根据一一对应算出num值
if (x%9) return x%9;
return 9;
}
void print(node x){
//便于输出结果
printf("%d%c",getnum(x.all),getch(x.all));
}
int cmp1(node x,node y){
//排序,先按照花色,再按照数值
if (x.ch==y.ch) return x.num<y.num;
return x.ch<y.ch;
}
bool check_sevenpairs(){
//判断七小对
int number=0;
for(int i=1;i<=34;i++)
//判断是1对还是两对,注意如果出现num[i]==3的情况是肯定false的所以不用特判
if (num[i]==2) number++;
else if (num[i]==4) number+=2;
if (number==7) return true;
return false;
}
bool check_Koku(){
//依次判断13种牌有没有
//再检查是不是有13种牌的某1种有两张
for(int i=28;i<=34;i++)
if (!num[i]) return false;
if (!num[1]) return false;
if (!num[9]) return false;
if (!num[10]) return false;
if (!num[18]) return false;
if (!num[19]) return false;
if (!num[27]) return false;
if (num[1]==2||num[9]==2||num[10]==2||num[18]==2||num[19]==2||num[27]==2
||num[28]==2||num[29]==2||num[30]==2||num[31]==2||num[32]==2||num[33]==2||num[34]==2) return true;
return false;
}
bool check_pairs(int steps){
if (steps==4){
//4句话枚举完毕了
//是不是真的把14张牌用完了
for(int i=1;i<=34;i++)
if (num[i]) return false;
return true;
}
for(int i=1;i<=34;i++)
if (num[i]){
//这张牌没有被枚举到4句话里面
if (num[i]>=3){
///AAA形式的,34张牌都可以
num[i]-=3;
if (check_pairs(steps+1)){
num[i]+=3;//记得恢复回来准备下一张牌的枚举
return true;
}
num[i]+=3;
}
if (num[i]&&i<=27){
//注意i<=27
//花牌没有ABC的情况
if (getch(i+1)==getch(i)&&getch(i+2)==getch(i)&&num[i+1]&&num[i+2]){
//这个地方一定要判断是同一个花色的
//因为在枚举中,9,10,11是连续的
//但是实际麻将中9万1条2条肯定不是1句话
num[i]--;num[i+1]--;num[i+2]--;
if (check_pairs(steps+1)){
num[i]++;num[i+1]++;num[i+2]++;
return true;
}
num[i]++;num[i+1]++;num[i+2]++;
}
}
return false;
}
}
int main(){
//input;
scanf("%d",&t);
while(t--){
memset(num,0,sizeof(num));
tot=0;
for(int i=1;i<=13;i++){
scanf("%s",s);
//一个数值上一一对应的计算
a[i].num=s[0]-'0';
if (s[1]=='m') a[i].ch=0;
else if (s[1]=='s') a[i].ch=1;
else if (s[1]=='p') a[i].ch=2;
else a[i].ch=3;
a[i].all=a[i].num+a[i].ch*9;
num[a[i].all]++;
}
for(int test=1;test<=34;test++){
flag=0;
if (num[test]==4) continue;
num[test]++;
if (check_sevenpairs()) flag=1;
if (check_Koku()) flag=1;
if (flag){
//如果是七小对或者十三幺
//就已经听牌不需要进行之后的判断了
tot++;
ans[tot].all=test;
num[test]--;
continue;
}
for(int i=1;i<=34;i++)
if (num[i]>=2){
num[i]-=2;
if (check_pairs(0)){
flag=1;
num[i]+=2;
//提前恢复
break;
}
num[i]+=2;
}
if (flag){
tot++;
ans[tot].all=test;
}
num[test]--;
}
if (tot){
printf("%d",tot);
for(int i=1;i<=tot;i++)
printf(" "),print(ans[i]);
printf("\n");
}
else cout<<"Nooten"<<endl;
}
return 0;
}
注意:上面的程序是过不了的
亲测HDOJ的测试数据,认为4个1万不算作两个对子
所以如果num【x】==4,是不算做两个对子的,这点得注意
其他的模拟都没有问题
另附上测试数据,比较给力的测出了自己的很多错误:
11
1s 1s 1s 1m 1m 4m 4m 7m 7m 9s 9s 5p 5p
1m 2m 3m 4m 5m 6m 7m 8m 9m 1c 2c 2c 2c
1c 2c 3c 4c 5c 6c 7c 1m 2m 3m 4m 5m 6m
1m 2m 3m 4m 5m 6m 7m 1s 1s 1s 2s 2s 2s
1m 1m 1m 2m 3m 4m 5m 6m 7m 8m 9m 9m 9m
1s 1s 1s 2s 3s 4s 5s 6s 7s 8s 9s 9s 9s
1m 2s 2s 3s 3s 4s 4s 5s 5s 6s 6s 7s 7s
1s 2s 3s 2c 2c 2c 2p 3p 5m 6m 7m 1p 1p
1s 9s 1m 9m 1p 9p 1c 2c 3c 4c 5c 6c 7c
1s 1s 1m 9m 1p 9p 1c 2c 3c 4c 5c 6c 7c
1p 1p 2p 3p 4s 5s 6s 7c 7c 3s 3s 2m 2m
其中第1组是Hack数据,龙七对听1s
第二组就是检测3c能不能听,1c2c3c是花牌不能作为一句话
第三组同理
其中还有九子连环的测试数据,希望能减少大家的提交次数
模拟刷起来!