斗地主
题目大意
牛牛最近迷上了一种叫斗地主的扑克游戏. 斗地主是一种使用黑桃、红心、梅花、方片的 A 到 K 加上大小王的共 54 张牌来进行的扑克牌游戏. 在斗地主中, 牌的大小关系根据牌的数码表示如下: 3<4<5<6<7<8<9<10
样例输入
1 8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1
样例输出
3
样例解释
共有 1 组手牌, 包含 8 张牌: 方片 7, 方片 8, 黑桃 9, 方片 10, 黑桃 J, 黑桃 5, 方片 A 以及黑桃 A. 可以通过打单顺子 (方片 7, 方片 8, 黑桃 9, 方片 10, 黑桃 J), 单张牌 (黑桃 5) 以及对子牌 (黑桃 A 以及方片 A) 在 3 次内打光.
规模
\(n <= 23\).
手牌不一定是随机生成的
在此题中认为两个王不能组成对子牌
时间限制: 2s 空间限制: 1GB
Explanation
这道题用 大 DFS, 小 DP, 交错暴力的方式做.
先大范围将所有单、双、三顺子全部取掉, 这个操作是要暴力搜寻的, 当然时间常数很大, 但是是 \(O(1)\) 的. 然后我们再大规模贪心 (贪心需要大量特判, 如果不仔细写的话, NOIP 官方的数据还是能过的, 但是一遇到 UOJ 的强力数据就会统统 WA 掉) 或者 DP 保证正确.
我不是拿 DP 写的, 所以就把一些奇怪的特判贴上来.
这个是在 UOJ 上能过 97 分的版本.
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
typedef long long lli;
const int infinit = 0x007f7f7f;
// 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A, 2, Joker.
class CardSet
{
public:
int data_1, data_2, data_3;
void clear(void) {
data_1 = data_2 = data_3 = 0; }
int get(int pos) {
return (((data_1 >> pos) & 1) << 2)
+ (((data_2 >> pos) & 1) << 1)
+ (((data_3 >> pos) & 1) << 0); }
void print(void) {
for (int i = 1; i <= 14; i++)
printf("%d ", this->get(i));
printf("\n"); }
void set(int pos, int dat) {
if (((data_1 >> pos) & 1) != ((dat >> 2) & 1))
data_1 ^= (1 << pos);
if (((data_2 >> pos) & 1) != ((dat >> 1) & 1))
data_2 ^= (1 << pos);
if (((data_3 >> pos) & 1) != ((dat >> 0) & 1))
data_3 ^= (1 << pos); }
void modify(int pos, int dat) {
set(pos, get(pos) + dat); }
CardSet clone(void) { CardSet b = *this; return b; }
};
int T, n;
int dp(CardSet in, int depth)
{
int cnt[5] = {0, 0, 0, 0, 0};
int res = 0;
for (int i = 1; i <= 14; i++)
cnt[in.get(i)]++;
// Trying all other combinations
while (cnt[4] >= 1 && cnt[2] >= 2) // Four with two pairs
cnt[4] -= 1, cnt[2] -= 2, res++;
while (cnt[4] >= 1 && cnt[1] >= 2) // Four with two singles
cnt[4] -= 1, cnt[1] -= 2, res++;
while (cnt[4] >= 1 && cnt[2] >= 1) // Four with two duplicate singles
cnt[4] -= 1, cnt[2] -= 1, res++;
while (cnt[3] >= 1 && cnt[1] >= 1) // Three with one single
cnt[3] -= 1, cnt[1] -= 1, res++;
while (cnt[3] >= 1 && cnt[2] >= 1) // Three with one pair
cnt[3] -= 1, cnt[2] -= 1, res++;
res += cnt[4] + cnt[3] + cnt[2] + cnt[1]; // Add single pairs
res += depth; // Iterative depth
return res;
}
int dfs(CardSet in, int depth)
{
int dfs_part_min = infinit;
#define minimize(__x) dfs_part_min = min(dfs_part_min, (__x))
// Dynamic programming
minimize(dp(in, depth));
// Two *3 in a row
for (int i = 1; i <= 14; i++) {
int j = i - 1;
while (j < 12 && in.get(j + 1) >= 3) j++;
if (j - i < 2 - 1) continue;
// Applying to clone
CardSet out = in.clone();
for (int k = i; k <= j; k++)
out.modify(k, -3);
minimize(dfs(out, depth + 1));
}
// Three *2 in a row
for (int i = 1; i <= 14; i++) {
int j = i - 1;
while (j < 12 && in.get(j + 1) >= 2) j++;
if (j - i < 3 - 1) continue;
// Applying to clone
CardSet out = in.clone();
for (int k = i; k <= j; k++)
out.modify(k, -2);
minimize(dfs(out, depth + 1));
}
// Five *1 in a row
for (int i = 1; i <= 14; i++) {
int j = i - 1;
while (j < 12 && in.get(j + 1) >= 1) j++;
if (j - i < 5 - 1) continue;
// Applying to clone
CardSet out = in.clone();
for (int k = i; k <= j; k++)
out.modify(k, -1);
minimize(dfs(out, depth + 1));
}
return dfs_part_min;
}
int main(int argc, char** argv)
{
CardSet cs;
scanf("%d%d", &T, &n);
for (int idx = 1; idx <= T; idx++) {
cs.clear();
for (int i = 1, a, b; i <= n; i++) {
scanf("%d%d", &a, &b);
if (a == 0) cs.modify(14, 1);
else if (a <= 2) cs.modify(11 + a, 1);
else cs.modify(a - 2, 1);
}
// Read card set, ready to work.
// Brute force all consequential combinations...
int res = dfs(cs, 0);
printf("%d\n", res);
}
return 0;
}
但是为什么会错呢 (雾)~
原因是这样的几组 hack 数据:
1 17
7 3
4 2
5 2
10 1
10 3
3 3
13 4
4 4
7 2
0 2
4 1
13 3
7 4
4 3
10 4
5 4
0 1
为什么没有过呢? 因为四带二是不能带上大小王的.
大小王也不能被三带二的 (换句话说大小王不是一种牌, 所以我们把它剔除出去)
1 8
1 1
1 2
1 3
1 4
2 1
2 2
2 3
2 4
为什么也没有过呢? 因为四带二可以一对四张牌把另一个四张牌拆成两对一起带走.
1 22
9 1
6 1
12 1
12 4
3 3
6 3
12 2
7 3
3 4
7 4
13 3
12 3
7 1
3 2
10 2
13 4
11 4
10 3
6 2
6 4
4 3
13 2
为什么还是没有过呢? 因为四带二可以拆掉一张三张牌的. 但是这样拆不一定具有贪心最优性, 所以我们就略微修改一下贪心的函数, 让它能够接受这样拆分 (也就是代码中的work_4_3
, 这个 变量代表了拆掉多少组 (4 张牌, 3 张牌), 然后 dfs+贪心搞定.
这样这道题就是过了 hack 的 AC 代码了 (是不是很吼啊)~
Example Code
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
typedef long long lli;
const int infinit = 0x007f7f7f;
// 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A, 2, Joker.
class CardSet
{
public:
int data_1, data_2, data_3;
void clear(void) {
data_1 = data_2 = data_3 = 0; }
int get(int pos) {
return (((data_1 >> pos) & 1) << 2)
+ (((data_2 >> pos) & 1) << 1)
+ (((data_3 >> pos) & 1) << 0); }
void print(void) {
for (int i = 1; i <= 14; i++)
printf("%d ", this->get(i));
printf("\n"); }
void set(int pos, int dat) {
if (((data_1 >> pos) & 1) != ((dat >> 2) & 1))
data_1 ^= (1 << pos);
if (((data_2 >> pos) & 1) != ((dat >> 1) & 1))
data_2 ^= (1 << pos);
if (((data_3 >> pos) & 1) != ((dat >> 0) & 1))
data_3 ^= (1 << pos); }
void modify(int pos, int dat) {
set(pos, get(pos) + dat); }
CardSet clone(void) { CardSet b = *this; return b; }
};
int T, n;
int dp(CardSet in, int depth, int work_4_3)
{
#define myp printf("cnt. %d %d %d %d\n", cnt[1], cnt[2], cnt[3], cnt[4])
int cnt[5] = {0, 0, 0, 0, 0};
int res = 0, final_res = infinit;
for (int i = 1; i <= 13; i++)
cnt[in.get(i)]++;
bool has_joker_pair = in.get(14) >= 2;
cnt[1] += in.get(14); // Jokers must not be counted as cards of the same type
// Examining branch conditions
if (cnt[4] >= 1 && cnt[3] >= 1 && work_4_3 <= 0)
for (int i = 1; i <= min(cnt[3], cnt[4]); i++)
final_res = min(final_res, dp(in, depth, i));
// Brute force work_4_3.
for (int i = 1; i <= work_4_3; i++) {
cnt[4] -= 1, cnt[3] -= 1, cnt[1] += 1, res++;
if (cnt[2] >= 1) cnt[2] -= 1; }
// Trying all other combinations
while (cnt[4] >= 1 && cnt[2] >= 2) // Four with two pairs
cnt[4] -= 1, cnt[2] -= 2, res++;
while (cnt[4] >= 1 && cnt[1] >= 2) // Four with two singles
cnt[4] -= 1, cnt[1] -= 2, res++;
while (cnt[4] >= 1 && cnt[2] >= 1) // Four with two duplicate singles
cnt[4] -= 1, cnt[2] -= 1, res++;
while (cnt[4] >= 2) // Four with two pairs of the same type
cnt[4] -= 2, res++;
while (cnt[3] >= 1 && cnt[1] >= 1) // Three with one single
cnt[3] -= 1, cnt[1] -= 1, res++;
while (cnt[3] >= 1 && cnt[2] >= 1) // Three with one pair
cnt[3] -= 1, cnt[2] -= 1, res++;
if (has_joker_pair && cnt[1] >= 2)
cnt[1] -= 2, res++;
res += cnt[4] + cnt[3] + cnt[2] + cnt[1]; // Add single pairs
res += depth; // Iterative depth
final_res = min(final_res, res);
// in.print();
// printf("result %d is %d\n", work_4_3, final_res);
return final_res;
}
int dfs(CardSet in, int depth)
{
int dfs_part_min = infinit;
#define minimize(__x) dfs_part_min = min(dfs_part_min, (__x))
// Dynamic programming
minimize(dp(in, depth, 0));
// Two *3 in a row
for (int i = 1; i <= 14; i++) {
int j = i - 1;
while (j < 12 && in.get(j + 1) >= 3) j++;
if (j - i < 2 - 1) continue;
// Applying to clones
CardSet out = in.clone();
for (int k = i; k < i + 1; k++)
out.modify(k, -3);
for (int k = i + 1; k <= j; k++) {
out.modify(k, -3);
minimize(dfs(out, depth + 1));
}
}
// Three *2 in a row
for (int i = 1; i <= 14; i++) {
int j = i - 1;
while (j < 12 && in.get(j + 1) >= 2) j++;
if (j - i < 3 - 1) continue;
// Applying to clone
CardSet out = in.clone();
for (int k = i; k < i + 2; k++)
out.modify(k, -2);
for (int k = i + 2; k <= j; k++) {
out.modify(k, -2);
minimize(dfs(out, depth + 1));
}
}
// Five *1 in a row
for (int i = 1; i <= 14; i++) {
int j = i - 1;
while (j < 12 && in.get(j + 1) >= 1) j++;
if (j - i < 5 - 1) continue;
// Applying to clone
CardSet out = in.clone();
for (int k = i; k < i + 4; k++)
out.modify(k, -1);
for (int k = i + 4; k <= j; k++) {
out.modify(k, -1);
minimize(dfs(out, depth + 1));
}
}
return dfs_part_min;
}
int main(int argc, char** argv)
{
CardSet cs;
scanf("%d%d", &T, &n);
for (int idx = 1; idx <= T; idx++) {
cs.clear();
for (int i = 1, a, b; i <= n; i++) {
scanf("%d%d", &a, &b);
if (a == 0) cs.modify(14, 1);
else if (a <= 2) cs.modify(11 + a, 1);
else cs.modify(a - 2, 1);
}
// Read card set, ready to work.
// Brute force all consequential combinations...
int res = dfs(cs, 0);
printf("%d\n", res);
}
return 0;
}