【题目链接】
NOIP 2015 D1T3 斗地主 UOJ 147 COGS 2106
【题目描述】
牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关 系根据牌的数码表示如下: 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A < 2 < 小王 < 大王 ,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由 nn 张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。
现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。
需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。具体规则如下:
|牌型 | 牌型说明 |牌型举例| | :----: | :--------------------------------------------------------: | :-------------------------: | | 火箭 | 即双王(双鬼牌) |♂ ♀ | 炸弹 | 四张同点牌 |♠A ♥A ♣A ♦A | 单张牌 | 单张牌 |♠3 | 对子牌 | 两张码数相同的牌 |♠2 ♥2 | 三张牌 | 三张码数相同的牌 |♠3 ♥3 ♣3 | 三带一 | 三张码数相同的牌 + 一张单牌 |♠3 ♥3 ♣3 ♠4 | 三带二 | 三张码数相同的牌 + 一对牌 |♠3 ♥3 ♣3 ♠4 ♥4 | 单顺子 | 五张或更多码数连续的单牌(不包括 2 点和双王) |♠7 ♣8 ♠9 ♣10 ♣J | 双顺子 | 三对或更多码数连续的对牌(不包括 2 点和双王) |♣3 ♥3 ♠4 ♥4 ♠5 ♥5 | 三顺子 | 二个或更多码数连续的三张牌(不能包括 2 点和双王) |♠3 ♥3 ♣3 ♠4 ♥4 ♣4 ♠5 ♦5 ♥5 | 四带二 | 四张码数相同的牌+任意两张单牌(或任意两对牌) |♠5 ♥5 ♣5 ♦5 ♣3 ♣8
(Markdown打表格累人 qwq )
【解题思路】
没参加过去年的NOIP,但早已听说过此题大名,于是在专门重新(以此为借口)跑去玩~~(颓)~~了一局斗地主之后,果断开写。
听说这题暴搜就可以过,然后我就暴搜啦。
有个简单的优化策略,即我们先考虑组合牌,然后在实在没有组合牌情况下再考虑单牌,这是因为如果决策定了,出牌顺序是无所谓的,对于最后剩下的单牌,我们一次性出完,直接把牌数累加进答案中,相当于一个类似贪心的策略。
组合牌的话,就枚举好了,据说枚举各种牌型时有一定讲究,先枚顺子,再枚四带,再枚三带,再枚对子,好像直觉上是这样的,没有深入探究过这是否很有效。
直接这样暴搜会T掉几个点,所以状压了一下开了记忆化,能够拿下。
因为每种牌的数量只有5种可能,牌的种类数只有14种,5 * 14 < 64,所以状态一个long long
就存的下,至于记忆化的表,直接使用 std::tr1::unordered_map
即可。
注意细节,关于火箭算不算对子这种事情,题目的确已经说明(->_->),对子的明确定义是两张码数相同的牌,而大小王的码数是明确的 小王<大王 的关系,所以大小王虽然能一起出(这是因为题目中定义了"火箭"这个牌型),但不是对子。
【AC代码】
poker数组中,位置0是小王,位置1是大王,位置[2, 14]是2~A 。 代码写的很长,很多地方都是重复的形式,应该还有不小压缩与改进的空间。
#include <cstdio>
#include <cstring>
#include <tr1/unordered_map>
#include <climits>
#define MAXN 15
#define update() ans = std::min(ans, search() + 1)
typedef long long State;
int poker[MAXN];
inline State zip(){
State s = 0;
for(int i = 0; i < MAXN; i++) s = s * 5 + poker[i];
return s;
}
std::tr1::unordered_map<State, int> M;
int search(){
State s = zip();
if(s == 0) return 0;
if(M.count(s)) return M[s];
int ans = INT_MAX;
bool onlySingle = true;
// jokers,最好特判一下
if(poker[0] && poker[1]){
poker[0] = poker[1] = 0;
update();
poker[0] = poker[1] = 1;
onlySingle = false;
}
// 单顺子
for(int i = 3; i < MAXN - 4; i++){
bool flag = false;
for(int d = 0; d < 5; d++) if(!poker[i + d]){
flag = true;
break;
}
if(flag) continue;
for(int d = 0; d < 5; d++) poker[i + d]--;
update();
int j;
for(j = i + 5; j < MAXN && poker[j]; j++){
poker[j]--;
update();
}
for(int k = i; k < j; k++) poker[k]++;
onlySingle = false;
}
// 双顺子
for(int i = 3; i < MAXN - 2; i++){
bool flag = false;
for(int d = 0; d < 3; d++) if(poker[i + d] < 2){
flag = true;
break;
}
if(flag) continue;
for(int d = 0; d < 3; d++) poker[i + d] -= 2;
update();
int j;
for(j = i + 3; j < MAXN && poker[j] >= 2; j++){
poker[j] -= 2;
update();
}
for(int k = i; k < j; k++) poker[k] += 2;
onlySingle = false;
}
// 三顺子
for(int i = 3; i < MAXN - 1; i++){
bool flag = false;
for(int d = 0; d < 2; d++) if(poker[i + d] < 3){
flag = true;
break;
}
if(flag) continue;
for(int d = 0; d < 2; d++) poker[i + d] -= 3;
update();
int j;
for(j = i + 2; j < MAXN && poker[j] >= 3; j++){
poker[j] -= 3;
update();
}
for(int k = i; k < j; k++) poker[k] += 3;
onlySingle = false;
}
// 三带
for(int i = 2; i < MAXN; i++) if(poker[i] >= 3){
poker[i] -= 3;
// 三不带
update();
// 三带一
for(int j = 0; j < MAXN; j++) if(poker[j] >= 1){
poker[j]--;
update();
poker[j]++;
}
// 三带对
for(int j = 2; j < MAXN; j++) if(poker[j] >= 2){
poker[j] -= 2;
update();
poker[j] += 2;
}
poker[i] += 3;
onlySingle = false;
}
// 四带
for(int i = 2; i < MAXN; i++) if(poker[i] == 4){
poker[i] -= 4;
// boom ! 炸弹,即四不带
update();
// 四带二
for(int j = 0; j < MAXN; j++) if(poker[j] >= 1){
poker[j]--;
for(int k = 0; k < MAXN; k++) if(poker[k] >= 1){
poker[k]--;
update();
poker[k]++;
}
poker[j]++;
}
// 四带双对
for(int j = 2; j < MAXN; j++) if(poker[j] >= 2){
poker[j] -= 2;
for(int k = 0; k < MAXN; k++) if(poker[k] >= 2){
poker[k] -= 2;
update();
poker[k] += 2;
}
poker[j] += 2;
}
poker[i] += 4;
onlySingle = false;
}
// 对子
for(int i = 2; i < MAXN; i++) if(poker[i] >= 2){
poker[i] -= 2;
update();
poker[i] += 2;
onlySingle = false;
}
// 只有在全是单牌时一次出光所有单牌
if(onlySingle){
ans = 0;
for(int i = 0; i < MAXN; i++){
ans += poker[i];
}
}
return M[s] = ans;
}
int main(){
// freopen("landlords.in", "r", stdin), freopen("landlords.out", "w", stdout);
int T, n;
scanf("%d%d", &T, &n);
while(T--){
memset(poker, 0, sizeof poker);
M.clear();
for(int i = 0; i < n; i++){
int x, y;
scanf("%d%d", &x, &y);
if(x == 0) poker[y - 1]++;
else if(x == 1) poker[14]++;
else poker[x]++;
}
printf("%d\n", search());
}
// fclose(stdin), fclose(stdout);
return 0;
}
就这样啦