JSOI2007 麻将 题解

时间: 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.

根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值