我寻思着这数据量也不大,直接枚举就完事儿了。答案 。
每张牌出现的次数在 之间,于是只有 种不同的状态。
把每种状态看成图论里的一个点,若 状态可以由 状态添加一张牌得到,则添加一条有向边 。
那么,考虑13张牌的任意状态 ,若 没听,则任何从 出发能走到的状态都不满足集合 的要求。这样一遍bfs就可以了,本质上是一个状压dp。
唯一要注意的是纯空听算不算听牌的问题。我的程序第一版只写了和牌判定算法,然后从所有状态里挑出和牌状态,再从和牌状态任意去掉一张来倒推听牌状态。这样做会影响 时解的数量。
所有 的解(23个):
111122223333444555 111222233334444555 111222333344445555 111222333444455556 122223333444555666 222233334444555666 222333344445555666 222333444455556666 222333444555566667 233334444555666777 333344445555666777 333444455556666777 333444555566667777 333444555666677778 344445555666777888 444455556666777888 444555566667777888 444555666677778888 444555666777788889 455556666777888999 555566667777888999 555666677778888999 555666777788889999
所有的X值对应的解的数量(假定纯空听视为听牌):
13: 93600 14: 16044 15: 2797 16: 589 17: 145 18: 23
附源码(C++20标准,g++ 10.2.0),欢迎帮忙debug:
#include <bits/stdc++.h> using namespace std; constexpr bool kAllowPureEmptyWait = true; class ScopedInc { public: explicit ScopedInc(unsigned char& c, int v = 1) : c_(c), v_(v) { c += v; } ~ScopedInc() { c_ -= v_; } private: unsigned char& c_; int v_; }; class Hand { public: explicit Hand(int i) { num_tiles_ = 0; for (auto& v : tiles_) { num_tiles_ += v = i % 5; i /= 5; } } int num_tiles() const { return num_tiles_; } bool is_waiting() { for (auto& v : tiles_) { if (!kAllowPureEmptyWait && v == 4) continue; ScopedInc s(v); if (is_winning()) return true; } return false; } void print() const { for (int i = 0; i < tiles_.size(); i++) { for (int j = 0; j < tiles_[i]; j++) { cout << i + 1; } } } vector<int> get_successors() { vector<int> ret; for (auto& v : tiles_) { if (v == 4) continue; ScopedInc s(v); ret.push_back(encode()); } return ret; } private: using Arr = array<unsigned char, 9>; using It = Arr::iterator; bool is_winning() { // Pair for (auto& v : tiles_) { if (v < 2) continue; ScopedInc s(v, -2); if (is_winning_without_pair(/*num_groups=*/0, tiles_.begin())) return true; } return false; } bool is_winning_without_pair(int num_groups, It start) { if (num_groups == 4) return true; for (It i = start; i != tiles_.end(); ++i) { // Identical group if (*i >= 3) { ScopedInc s(*i, -3); if (is_winning_without_pair(num_groups + 1, i)) return true; } // Sequential group if (tiles_.end() - i >= 3 && i[0] > 0 && i[1] > 0 && i[2] > 0) { ScopedInc s0(i[0], -1), s1(i[1], -1), s2(i[2], -1); if (is_winning_without_pair(num_groups + 1, i)) return true; } } return false; } int encode() const { int ret = 0; for (auto v : tiles_ | views::reverse) { ret = ret * 5 + v; } return ret; } Arr tiles_; int num_tiles_; }; int main() { constexpr int N = 5*5*5*5*5*5*5*5*5; queue<int> q; vector<int> failure_source(N); for (int i = 0; i < N; i++) { Hand hand(i); if (hand.num_tiles() == 13 && !hand.is_waiting()) { q.push(i); failure_source[i] = i; } } vector<bool> ok(N, true); while (!q.empty()) { Hand hand(q.front()); for (int successor : hand.get_successors()) { if (ok[successor]) { q.push(successor); ok[successor] = false; failure_source[successor] = failure_source[q.front()]; } } q.pop(); } int best_hand_v = -1, max_tiles = 0; for (int i = 0; i < N; i++) { int num_tiles = Hand(i).num_tiles(); if (num_tiles < 13) continue; if (ok[i] && num_tiles > max_tiles) { best_hand_v = i; max_tiles = num_tiles; } } cout << "Best max tiles: " << max_tiles << endl; map<int, int> counts; for (int i = 0; i < N; i++) { Hand hand(i); int num_tiles = hand.num_tiles(); if (num_tiles < 13 || !ok[i]) continue; ++counts[num_tiles]; if (num_tiles == max_tiles) { hand.print(); cout << endl; } } cout << "# of solutions by # of tiles: " << endl; for (auto [num_tiles, count] : counts) { cout << num_tiles << ": " << count << endl; } return 0; }
A(18)={333444455556666777}.
已手动枚举过是成立的。再增加一个3或者7,存在3333444466667的反例。再增加3个8,存在3345556677788的反例。
补充一下枚举过程:首先至少有1枚3和1枚7,否则转化为4种手牌的情况,已被火警证明过。为节省计算量,仅列出每种数牌的枚数以及其中1种听牌,如3444555566667记作13441,听3。
①1枚3+1枚7:
13441→1333,听3
14341→313,听5
②1枚3+2枚7:
12442→1342→232,听7
13342→2242→22,听7
13432→2332,听7
14242→1102,听2
14332→232,听7
14422→22,听7
③1枚3+3枚7:
11443→1111,听3
12343→124,听7
12433→124,听3
13243→214,听4
13333,听3
13423→232,听4
14233→142,听3
14323→3223,听5
14413→1111,听3
④2枚3+2枚7:
21442→21112:听7
22342→142,听5
22432→232,听7
23242→2002,听3
23332,听3
⑤2枚3+3枚7:
20443→2011,听4
21343→2101,听5
21433→214,听3
22243→2221,听3
22333,听3
22423→1102,听2
23143→2011,听4
23233,听3
23323,听3
23413→2011,听4
24043→2101,听5
24133→211,听3
24223→2122,听7
24313→2101,听5
24403→211,听3
⑥3枚3+3枚7:
30343→313,听4
30433→301,听4
31243→121,听5
31333,听4
31423→112,听3
32143→214,听4
32233,听4
32323,听4
32413→211,听4
33043→13,听5
33133,听5
枚举完毕,故X≥18.
仍然先从结论来说:当集合A中包含四个连刻时,X =18。(刻子是三张相同的牌组成的面子,四连刻是指四个连续的刻子。例如:333444555666)
这里给出三个X=18的例子:A(18)={233334444555666777},{222333444455556666},{33344455556666777}。且他们平移或左右翻折后仍然可行。
具体求解过程用了递推和枚举遍历,由于过于复杂,就不做说明了。
(题目经过一次修改,原题目为:麻将中同一花色的牌有 36 张,在 36 中选取一个整数值 x,13≤x≤36,那么这个整数 x 是否存在一个最大值 X,从 36 张一色牌中任意取出 X 张牌形成集合 A,使得手牌在门清状态下任意从 A 中选取 13 张手牌,都能形成一般形的清一色听牌,如果存在,这个最大值 X 是多少,请举出一个集合 A 的例子。)
从结论来说,不存在这样的X,使X满足题目中的条件。(后附问题修改建议)
1)证明:
首先找到一个13张牌的集合P,使P不听牌,不妨令P={1133445667799}。
假设,存在一个整数x [13,36],从36张牌中“任取”x张牌,使得这x张牌组成的集合A中的“任意”13张牌都是听牌。
既然是“任取”,那不妨令A P。
于是A中则有一种13张牌的组合P不是听牌,与假设矛盾。
故,不存在这样的x,使x满足题目中的条件。
2)问题修改建议:
麻将中同一花色的牌有 36 张,在 36 中选取一个整数值 x,13≤x≤36,那么这个整数 x 是否存在一个最大值 X,从 36 张一色牌中【存在一种】 X 张牌形成集合 A,使得手牌在门清状态下任意从 A 中选取 13 张手牌,都能形成一般形的清一色听牌,如果存在,这个最大值 X 是多少,请举出一个集合 A 的例子。
抛砖引玉一下,盲猜 X = 16
A(16) = {3333444455556666}
分情况讨论如下:
A(无33) = {33444455556666} = {33, 444, 555, 666, 456} → 和牌
A(无44) = {33334455556666} = {333, 345, 456, 55, 666} → 和牌
A(无34) = {33344455556666} = {333, 44, 456, 555, 666} → 和牌
A(无35) = {33344445556666} = {333, 444, 456, 55, 666} → 和牌
A(无36) = {33344445555666} = {333, 444, 456, 555, 66} → 和牌
A(无45) = {33334445556666} = {333, 345, 456, 456, 66} → 和牌
因为所有包含于 A(16) 的 A(14) 都是和牌,所以任意包含于 A(16) 的 A(13) 都是听牌。