晨晨和小璐最近在一副麻将牌的基础上,设计出了一款不知怎么取名字的游戏,游戏具体规则如下:
游戏使用以下 $27$ 张麻将牌中某些牌型的全部牌进行,这 $27$ 张牌分为万牌、条牌、饼牌三种牌型,每种牌型 $9$ 张,分别为一万 ($\texttt{1W}$)、二万 ($\texttt{2W}$)、……、九万 ($\texttt{9W}$);一条 ($\texttt{1T}$)、二条 ($\texttt{2T}$)、……、九条 ($\texttt{9T}$);一饼 ($\texttt{1B}$)、二饼 ($\texttt{2B}$)、……、九饼 ($\texttt{9B}$),每张牌有且仅有一张。这副麻将牌比较特殊,每张牌背面都标有这张牌的牌型 (万、条或饼)。
两名游戏玩家,相对而坐,游戏开始时,每个人分配等量的若干张牌,剩余的牌叠好,构成一个牌库。玩家将牌自左向右排成一排。任何时刻都保证:玩家面前任意两张相邻的牌,较小的那张牌在左侧。
任意两张牌的大小定义为:若数字不相同,则数字较小的牌较小;若数字相同,万牌 ($\texttt{W}$) 比条牌 ($\texttt{T}$) 小,万牌($\texttt{W}$)、条牌 ($\texttt{T}$) 都比饼牌 ($\texttt{B}$) 小。
任何时刻,每个人知道自己所有牌的信息,还知道对方每一张牌的牌型,即知道对方的每一张牌是万牌/条牌/饼牌。
游戏开始之后,双方轮流进行自己的回合。每回合中,当前进行回合的玩家先从牌库最上面摸一张牌并观看,之后按照自己的猜牌策略 (详见后述),指定对方的一张牌,并猜其数字是多少。
如果猜对了,对方那张被猜中的牌就会被亮出,之后当前进行回合的玩家将自己摸进的这张牌按照大小插入自己的牌序列中适当的位置;如果猜错了,作为惩罚,需将自己刚摸进的这张牌亮出,之后将自己摸进的这张牌按照大小插入自己的牌序列中适当的位置。
特殊地,如果牌堆里的牌已经摸完,而游戏尚未结束,则回合中不再进行摸牌,只进行猜牌,若猜对,对方被猜中的牌就会被亮出,若猜错则什么也不会发生。另外,游戏中玩家不能猜对方的某张牌是目前已经被自己摸到的某张牌。
对于一方被亮出的牌,另一方能知道这张牌是什么数字 (这张牌的牌型在亮出前另一方已经知道)。同时,玩家能够看到对方的牌序列发生的变化,即知道新摸进的牌插入的位置。双方玩家各自知道的信息有:自己的牌、自己对对方的哪张牌猜过哪些数字、对方每张牌的牌型、对方已经亮出的牌的数字,对方不会猜已经摸到的牌,但不知道对方的猜牌策略。
某一时刻,如果某玩家所有牌已经全数亮出,则该玩家输,游戏结束。
晨晨的猜牌策略是:每次都猜小璐的牌序列中最小的未亮出的牌,猜的数字是根据自己掌握的信息,计算出的可能的最大数;
小璐的猜牌策略是:每次都猜晨晨的牌序列中最大的未亮出的牌,猜的数字是根据自己掌握的信息,计算出的可能的最小数。
现在给出游戏开始时双方分配到的牌,牌库从顶至底的牌的顺序,请回答:若晨晨先进行回合,谁是胜利者,游戏结束时,获胜者还有多少张牌没有亮出来。
输入包含三行,每行一个非空字符串。
第一行包含一个字符串,描述游戏开始时,晨晨手里的若干张牌。
第二行包含一个字符串,描述游戏开始时,小璐手里的若干张牌。
第三行依次描述的是牌堆自顶而下每张牌,保证无多余空格与回车。
描述格式如下:每个字符串都由 $2n$ 个字符组成,每两个字符表示一张牌,第一个字符为牌的数字,第二个字符为牌的牌型,例如 1W、6T、9B,分别表示一万、六条、九饼。
输出两行,第一行表示谁是胜利者,如果是晨晨,则输出 C;如果是小璐,则输出 L。
第二行输出一个整数,表示游戏结束时,获胜者还有多少张牌没有亮出来。
由于只有区区 $27$ 张 (麻将) 牌,所以直接按照题意模拟即可。
注意到一张牌有数字、牌型和是否公开三个关键字,因此可以使用联合结构体 union {char[3], int} 来存储。如果是小端序的话,这样这个 int 的比较就等效于原来牌的比较。
注意读进来的牌、包括新插入的牌需要排序,以及排序规则。
#include <bits/stdc++.h>
#define N 120
#define publ v[0]
#define type v[1]
#define size v[2]
#define highbit(x) (31 - __builtin_clz(x))
#define lowbit __builtin_ctz
union card {char v[3]; int value;};
int n_scx, n_fy, top;
char _scx[N], _fy[N], _s[N];
card scx[N], fy[N], s[N];
int sg[3][10], fg[3][10];
bool ok[3][10];
inline void up(int &x, const int y) {x < y ? x = y : 0;}
inline void down(int &x, const int y) {x > y ? x = y : 0;}
inline bool operator < (const card A, const card B) {return A.value < B.value;}
inline card get_card(char si, char ty) {card ret; ret.value = 0; ret.publ = 0; ret.size = si; ret.type = ty; return ret;}
inline card str2card(const char *p) {return get_card(*p & 15, p[1] == 66 ? 2 : p[1] == 84);}
void initialize() {
int i, j;
scanf("%s%s%s", _scx, _fy, _s);
n_scx = strlen(_scx) / 2;
n_fy = strlen(_fy) / 2;
top = strlen(_s) / 2;
for (i = 0; i < top; ++i) s[top - i - 1] = str2card(_s + i * 2);
for (i = 0; i < n_scx; ++i) scx[i] = str2card(_scx + i * 2);
for (i = 0; i < n_fy; ++i) fy[i] = str2card(_fy + i * 2);
std::sort(scx, scx + n_scx); std::sort(fy, fy + n_fy);
for (i = 0; i < 3; ++i)
for (j = 1; j < 10; ++j) sg[i][j] = fg[i][j] = 0x3fe;
}
int scx_guess(int id) {
int i, j = 0, k = 10, l;
memset(ok, true, sizeof ok);
for (i = 0; i < n_scx; ++i) ok[scx[i].type][scx[i].size] = false;
for (i = 0; i < n_fy; ++i) if (fy[i].publ) ok[fy[i].type][fy[i].size] = false;
for (i = n_fy - 1; i >= id; --i)
if (fy[i].publ) k = fy[i].size, j = fy[i].type;
else {
if (fy[i].type >= j) --k; j = fy[i].type;
for (l = 0; k != l; ) {
sg[j][fy[i].size] &= ~(-1 << k + 1); l = k;
for (down(k, highbit(sg[j][fy[i].size])); k > 1 && !ok[j][k]; --k);
}
}
return k;
}
int fy_guess(int id) {
int i, j = 3, k = 0, l;
memset(ok, true, sizeof ok);
for (i = 0; i < n_fy; ++i) ok[fy[i].type][fy[i].size] = false;
for (i = 0; i < n_scx; ++i) if (scx[i].publ) ok[scx[i].type][scx[i].size] = false;
for (i = 0; i <= id; ++i)
if (scx[i].publ) k = scx[i].size, j = scx[i].type;
else {
if (scx[i].type <= j) ++k; j = scx[i].type;
for (l = 0; k != l; ) {
fg[j][scx[i].size] &= -1 << k; l = k;
for (up(k, lowbit(fg[j][scx[i].size])); k < 9 && !ok[j][k]; ++k);
}
}
return k;
}
void scx_play() {
int i, k = -1, id, si, ty;
card c, nw;
if (top) {
nw = s[--top];
for (k = 0; k < n_scx && scx[k].value < nw.value; ++k);
for (i = n_scx; i > k; --i) scx[i] = scx[i - 1];
scx[k] = nw; ++n_scx;
}
for (id = 0; id < n_fy; ++id) if (!fy[id].publ) break;
ty = fy[id].type;
si = scx_guess(id);
c = get_card((char)si, (char)ty);
if (fy[id].value == c.value) fy[id].publ = 1;
else {
if (~k) scx[k].publ = 1;
sg[ty][fy[id].size] &= ~(-1 << si);
for (i = 0; i < n_scx; ++i)
if (scx[i].type == ty)
fg[ty][scx[i].size] &= ~(1 << si);
}
}
void fy_play() {
int i, k = -1, id, si, ty;
card c, nw;
if (top) {
nw = s[--top];
for (k = 0; k < n_fy && fy[k].value < nw.value; ++k);
for (i = n_fy; i > k; --i) fy[i] = fy[i - 1];
fy[k] = nw; ++n_fy;
}
for (id = n_scx - 1; id >= 0; --id) if (!scx[id].publ) break;
ty = scx[id].type;
si = fy_guess(id);
c = get_card((char)si, (char)ty);
if (scx[id].value == c.value) scx[id].publ = 1;
else {
if (~k) fy[k].publ = 1;
fg[ty][scx[id].size] &= -2 << si;
for (i = 0; i < n_fy; ++i)
if (fy[i].type == ty)
sg[ty][fy[i].size] &= ~(1 << si);
}
}
bool check_scx() {for (int i = 0; i < n_scx; ++i) if (!scx[i].publ) return true; return false;}
bool check_fy() {for (int i = 0; i < n_fy; ++i) if (!fy[i].publ) return true; return false;}
int main() {
int i, j = 0;
initialize();
for (; ; ) {
scx_play();
if (!check_fy()) {putchar(67); for (i = 0; i < n_scx; ++i) j += !scx[i].publ; break;}
fy_play();
if (!check_scx()) {putchar(76); for (i = 0; i < n_fy; ++i) j += !fy[i].publ; break;}
}
printf("\n%d\n", j);
return 0;
}
易错点大杂烩时间到!
坑1:注意排序规则为先比较数字,再比较牌型,而不是先比较牌型。因此不要被样例所迷惑了。
坑2:猜牌时注意是先摸再猜而非先猜再摸。
坑3:注意推测是,大小是受花色影响的。比如,$\texttt{5W}$ 后面的 $\texttt{T}$,可能是 $\texttt{5T}$,而 $\texttt{5T}$ 后面的 $\texttt{W}$,至少为 $\texttt{6W}$。
坑4:如果一方在一轮中猜过牌 $s$,则另一方可以知道它这段时间内没有牌 $s$,除非,她觉得可能出现牌 $s$ 的位置中多了一张与之牌型相同的牌。
坑5:当一方摸到新牌 $f$ 时,对方只需要把可能 $f$ 的所有牌取消禁止标记即可,其余牌不用更改。比如,一方有 $\texttt{5T}\ x\texttt{W}\ x\texttt{W}$,她后面又多了个 $\texttt{W}$,则这个 $\texttt{W}$ 只有可能是 $\texttt{8W}$ 或 $\texttt{9W}$,也就是说 $\texttt{6W}$ 和 $\texttt{7W}$ 的禁止标记不用取消。