您的位置:首页 > Web前端 > JavaScript

JSOI2007 麻将 题解

2015-08-29 16:12 507 查看
时间: 1000ms / 空间: 131072KiB / Java类名: Main


描述

  麻将是中国传统的娱乐工具之一。麻将牌的牌可以分为字牌(共有东、南、西、北、中、发、白七种)和序数牌(分为条子、饼子、万子三种花色,每种花色各有一到九的九种牌),每种牌各四张。在麻将中,通常情况下一组和了的牌(即完成的牌)由十四张牌组成。十四张牌中的两张组成对子(即完全相同的两张牌),剩余的十二张组成三张一组的四组,每一组须为顺子(即同花色且序数相连的序数牌,例如条子的三、四、五)或者是刻子(即完全相同的三张牌)。一组听牌的牌是指一组十三张牌,且再加上某一张牌就可以组成和牌。那一张加上的牌可以称为等待牌。

  

在这里,我们考虑一种特殊的麻将。在这种特殊的麻将里,没有字牌,花色也只有一种。但是,序数不被限制在一到九的范围内,而是在1到n的范围内。同时,也没有每一种牌四张的限制。一组和了的牌由3m + 2张牌组成,其中两张组成对子,其余3m张组成三张一组的m组,每组须为顺子或刻子。现给出一组3m + 1张的牌,要求判断该组牌是否为听牌(即还差一张就可以和牌)。如果是的话,输出所有可能的等待牌。


输入格式

输入文件包含两行。

第一行包含两个由空格隔开整数n, m (9<=n<=400, 4<=m<=1000)。第二行包含3m + 1个由空格隔开整数,每个数均在范围1到n之内。这些数代表要求判断听牌的牌的序数。


输出格式

输出为一行。

如果该组牌为听牌,则输出所有的可能的等待牌的序数,数字之间用一个空格隔开。所有的序数必须按从小到大的顺序输出。如果该组牌不是听牌,则输出"NO"。


测试样例1


输入

9 4 

1 1 2 2 3 3 5 5 5 7 8 8 8


输出

6 7 9

【解题思路】

一、枚举胡的牌,枚举哪个当一对,从左到右简单贪心扫描一遍即可,贪心是指可以取刻子的就取刻子,剩下的去顺子,O(n3)
二、枚举胡的牌,f[I,p,j,k]:i=0表示已找到了对子,i=1表示没找到,已计算了前p位,p+1等价于有j张牌,p+2等价于有k张牌的情况是否可能出现。 O(n2)

三、f[I,o,p,j,k]:o=0表示加了要胡的牌,o=1表示没加要胡的牌,i=0表示已找到了对子,i=1表示没找到,已计算了前p位,p+1等价于有j张牌,p+2等价于有k张牌的情况是否可能出现。 O(n)

【分析】

看到题我首先想到的是枚举,我们可以依次枚举每个数字,即要听的牌,看加上这个数字之后能不能和,能的话记录下来,不能的话继续枚举下一个数字。那问题就在于如何来判断加上枚举的牌能否和。

首先,在我们判断的时候,每个数字只有两种情况:

1、它是刻子(即它有三个)

2、它是顺子的一个(此处我们直接判断第一个即可)

然后在我们判断这种情况能否成立的时候,首先我们应该先枚举找将(也就是那个对子),找到将之后,去掉将(把这两个数的次数减掉),然后在这个将的情况下去从前往后枚举每一个数字,对每个数字进行判断是否满足上面两种情况。这样的话,第一种很简单(a[i]:=a[i] mod 3;),我们只需看第二种即可,满足的话减掉这三个数的次数,如果已经是倒数第二个数或者最后一个,后面已经不能有两个数了,但如果此时它仍然不为0的话证明肯定胡不了,所以退出,枚举下一个麻将。

最后如果没有数字能成立的话输出no,能成立的话输出能听的牌。枚举完听牌之后输出。

【代码详解】

var

 a,b,w:array[1..400]of longint;

 m,n,t,i,j,k,tot:longint;

 ok:boolean;

begin

 readln(n,m);

 fori:= 1 to m*3+1 do

 begin

  read(t);

  inc(a[t]);  //a数组用来记录每个数出现的次数

 end;

 tot:=0;

 fori:= 1 to n do   //枚举听牌

 begin

  inc(a[i]);  //把枚举的数加上

  for j:= 1 to n do

   if a[j]>=2 then  //枚举将

    begin

     b:=a;  //我们用数组b来判断,防止a数组弄乱

     b[j]:=b[j]-2;  //减去将

     ok:=true;  //先将ok的值记为true

     for k:= 1 to n do //从头到尾来模拟能否胡

      begin

       b[k]:=b[k] mod 3;//减掉刻子

        if (k+2>n)and(b[k]<>0) then{k+2>n证明这已经是倒数第二个数或者最后一个,后面已经不能有两个数了,}

        begin

         ok:=false;  //但如果此时它仍然不为0的话证明肯定胡不了

         break;  //胡不了所以退出,来枚举另一个将

        end;

       if (b[k+1]<b[k])or(b[k+2]<b[k]) then//后面两个数的次数任何一个比它大都不可能胡

        begin

         ok:=false;

         break;

        end;

       b[k+1]:=b[k+1]-b[k]; 

       b[k+2]:=b[k+2]-b[k];

      end;

     if ok then break;

    end;

   dec(a[i]);//把刚刚枚举的听牌还原

   if ok then  //如果能胡的话记录

    begin

     inc(tot);

     w[tot]:=i;

    end;

 end;

  iftot=0 then writeln('NO') else  

 begin

   fori:= 1 to tot-1 do

    write(w[i],' ');

  writeln(w[tot]);

 end;

end.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: