P2540 [NOIP 2015 提高组] 斗地主 加强版 题解
强烈建议在 cnblogs 上查看代码。
不知道是啥做法。
可能是双向广搜或者 IDA?
Solution:
首先将 \(1\) 点变为 \(14\) 点,其余不变。
发现题目要求我们求最小出牌次数。
因为 BFS 天然满足如果搜到即为最优解的性质,所以考虑 BFS 解决。
我们思考一下或参考题解,发现该题状态集合具有对称性,所以这其实是双向 BFS。
发现点数最大只有 \(14\),每点牌数最多只有 \(4\),考虑用 \(5\) 进制存下牌集,每一位表示点数为这一位的牌的数量。
发现更新状态比较复杂,考虑大力 DFS。
对于每一个还未打出的点数,我们需要分情况讨论:
考虑不构成顺子情况。
若有 \(\ge 4\) 张牌,则考虑:直接打炸弹、四带两个一、四带两个对、拆成三、拆成两个对。
若有 \(\ge 3\) 张牌,则考虑:直接打三、三带一、三带二。
若有 \(\ge 2\) 张牌,则考虑:直接打对、四带两个对、三带二、四带一对(四带两个单)。
若有 \(\ge 1\) 张牌,则考虑:直接打单、四带两个单,三带一。
考虑可能构成顺子情况。
若有 \(\ge 4\) 张牌,则考虑:无。
若有 \(\ge 3\) 张牌,则考虑:若能算上这对牌打出三顺,则更新打出去时的最小次数。
若有 \(\ge 2\) 张牌,则考虑:若能算上这对牌打出对顺,则更新打出去时的最小次数。
若有 \(\ge 1\) 张牌,则考虑:若能算上这对牌打出单顺,则更新打出去时的最小次数。
上述情况中,若能打出符合条件的牌,就更新打出去时的最小次数,将新状态推进队尾。并向下 DFS 继续尝试打出后面牌后能否符合上述情况。
若在更新答案时发现未打出的牌集有最小次数,那么更新最终答案,和记录现在打出的牌的最小次数。我们必须要把所有的次数与最小次数相等的牌集都考虑进去,否则答案可能不是最优。
由于每次更新答案时次数只会加一,而答案又有对称性,所以这份代码加一点剪枝即可通过本题。
代码实现时比较复杂,需要考虑很多细节。
Code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
int x = 0, c = getchar(), f = 0;
for (; c > '9' || c < '0'; f = c == '-', c = getchar())
;
for (; c >= '0' && c <= '9'; c = getchar())
x = (x << 1) + (x << 3) + (c ^ 48);
return f ? -x : x;
}
unordered_map<ll, int> mp;
// mp[s] : 所有出过的牌的状态为 s 时,达成这个状态的最小次数。
int a[30]; // 排集
int n;
const ll ksm[15] = {1, 5, 25, 125, 625, 3125, 15625, 78125, 390625, 1953125, 9765625, 48828125, 244140625, 1220703125, 6103515625};
// 5 进制状压
// 每一位存
int t[16] = {};
// 存每个点数出现了多少张牌
int st[30] = {}, tail = 0;
// 存手里还剩的牌的点数
int ans = 0, ans_cnt = 0;
// ans: 最小次数 ; ans_cnt:最小次数由哪个 cnt 被更新过来
struct Node
{
int cnt; // 现在打了多少次牌
ll use; // use:所有出过的牌的状态
ll unuse; // unuse:所有没出的牌的状态
};
queue<Node> q; // BFS
// 更新答案
inline void get(const int &cnt, ll use, ll unuse)
{
if (mp.count(use))
return; // 广搜一定是更优的状态先被搜到,所以之前搜到过则直接返回
mp[use] = mp[use] == 0 ? cnt : min(cnt, mp[use]); // 更新打出牌状态为 use 的最小次数
if (mp.count(unuse)) // 如果
ans = (ans == 0 ? cnt + mp[unuse] : min(ans, cnt + mp[unuse])), ans_cnt = cnt;
else
q.push({cnt, use, unuse});
}
/*
一共打了 cnt 次,
现在要选择打或不打 st[pos] 这张牌,
到现在出的牌的状态为 use,没出过的牌的状态为 unuse,
一共打了
cnt1 个单,
cnt2 个对
cnt3 个三
cnt4 个四
,
上一个打出的牌是 last,若没有则为 -114514,
pd 记录可否构成顺子
*/
void dfs(const int &cnt, int pos, ll use, ll unuse, int cnt1, int cnt2, int cnt3, int cnt4, int last, bool pd)
{
if (ans && cnt != ans_cnt)
return; // 非最优,返回
if (pos > tail)
return; // 没牌返回
int num = st[pos];
int c = t[num];
if (num == 0)
{
get(cnt, use + 1, unuse - 1); // 0
if (c == 2) // 0+0
get(cnt, use + 2, unuse - 2);
dfs(cnt, pos + 1, use + 1, unuse - 1, 1, cnt2, cnt3, cnt4, 0, 0); // 0 + ?
dfs(cnt, pos + 1, use, unuse, cnt1, cnt2, cnt3, cnt4, last, pd); // ?
return;
}
if (c >= 4)
{
if (cnt1 == 2 && !cnt2 && !cnt3 && !cnt4) // 4+1+1
get(cnt, use + ksm[num] * 4, unuse - ksm[num] * 4);
if (!cnt1 && cnt2 == 2 && !cnt3 && !cnt4) // 4+2+2
get(cnt, use + ksm[num] * 4, unuse - ksm[num] * 4);
if (!cnt1 && !cnt2 && !cnt3 && !cnt4) // 4+0
get(cnt, use + ksm[num] * 4, unuse - ksm[num] * 4);
if (cnt1 == 1 && !cnt2 && !cnt3 && !cnt4) // 4+1+?
dfs(cnt, pos + 1, use + ksm[num] * 4, unuse - ksm[num] * 4, cnt1, cnt2, cnt3, cnt4 + 1, num, 0);
if (!cnt1 && cnt2 == 1 && !cnt3 && !cnt4) // 4+2+?
dfs(cnt, pos + 1, use + ksm[num] * 4, unuse - ksm[num] * 4, cnt1, cnt2, cnt3, cnt4 + 1, num, 0);
if (!cnt1 && !cnt2 && !cnt3 && !cnt4)
{
dfs(cnt, pos + 1, use + ksm[num] * 4, unuse - ksm[num] * 4, cnt1, cnt2, cnt3, cnt4 + 1, num, 0); // 4+?
dfs(cnt, pos + 1, use + ksm[num] * 4, unuse - ksm[num] * 4, cnt1, cnt2 + 2, cnt3, cnt4, num, 0); // 2+2+?
}
}
if (c >= 3)
{
if (cnt1 == 1 && !cnt2 && !cnt3 && !cnt4) // 3+1
get(cnt, use + ksm[num] * 3, unuse - ksm[num] * 3);
if (!cnt1 && cnt2 == 1 && !cnt3 && !cnt4) // 3+2
get(cnt, use + ksm[num] * 3, unuse - ksm[num] * 3);
if (!cnt1 && !cnt2 && !cnt3 && !cnt4) // 3
get(cnt, use + ksm[num] * 3, unuse - ksm[num] * 3);
if (!cnt1 && !cnt2 && !cnt3 && !cnt4 && (!pd || (pd && (last < 0 || last == 2 || num != last + 1)))) // 3+? 且不可能构成顺子(剪枝)
dfs(cnt, pos + 1, use + ksm[num] * 3, unuse - ksm[num] * 3, cnt1, cnt2, cnt3 + 1, cnt4, num, 0);
}
if (c >= 2)
{
if (!cnt1 && cnt2 == 1 && !cnt3 && cnt4 == 1) // 2+4+2
get(cnt, use + ksm[num] * 2, unuse - ksm[num] * 2);
if (!cnt1 && !cnt2 && cnt3 == 1 && !cnt4) // 2+3
get(cnt, use + ksm[num] * 2, unuse - ksm[num] * 2);
if (!cnt1 && !cnt2 && !cnt3 && cnt4 == 1) // 1+1+4
get(cnt, use + ksm[num] * 2, unuse - ksm[num] * 2);
if (!cnt1 && !cnt2 && !cnt3 && !cnt4) // 2
get(cnt, use + ksm[num] * 2, unuse - ksm[num] * 2);
if (!cnt1 && !cnt2 && !cnt3 && (!pd || (pd && (last < 0 || last == 2 || num != last + 1)))) // 2+? // 2+4+? 且不可能构成顺子(剪枝)
dfs(cnt, pos + 1, use + ksm[num] * 2, unuse - ksm[num] * 2, cnt1, cnt2 + 1, cnt3, cnt4, num, 0);
if (!cnt1 && cnt2 == 1 && !cnt3 && !cnt4 && (!pd || (pd && (last < 0 || last == 2 || num != last + 1)))) // 2+2+?(4) 且不可能构成顺子(剪枝)
dfs(cnt, pos + 1, use + ksm[num] * 2, unuse - ksm[num] * 2, cnt1, cnt2 + 1, cnt3, cnt4, num, 0);
}
if (c >= 1)
{
if (!cnt1 && !cnt2 && cnt3 == 1 && !cnt4)
get(cnt, use + ksm[num] * 1, unuse - ksm[num] * 1); // 1+3
if (cnt1 == 1 && !cnt2 && !cnt3 && cnt4 == 1)
get(cnt, use + ksm[num] * 1, unuse - ksm[num] * 1); // 1+1+4
if (!cnt1 && !cnt2 && !cnt3 && !cnt4)
get(cnt, use + ksm[num] * 1, unuse - ksm[num] * 1); // 1+0
if (!cnt1 && !cnt2 && !cnt3 && cnt4 <= 1 && (!pd || (pd && (last < 0 || last == 2 || num != last + 1)))) // 1+? // 1+4+? 且不可能构成顺子(剪枝)
dfs(cnt, pos + 1, use + ksm[num] * 1, unuse - ksm[num] * 1, cnt1 + 1, cnt2, cnt3, cnt4, num, 0);
if (cnt1 == 1 && !cnt2 && !cnt3 && !cnt4 && (!pd || (pd && (last < 0 || last == 2 || num != last + 1)))) // 1+1+?(4) 且不可能构成顺子(剪枝)
dfs(cnt, pos + 1, use + ksm[num] * 1, unuse - ksm[num] * 1, cnt1 + 1, cnt2, cnt3, cnt4, num, 0);
}
if (num > 2 && pd) // 可能构成顺子
{
if ((last + 1 == num && last != 2) || last < 0) // 可能构成顺子
{
if (c >= 3 && !cnt1 && !cnt2 && !cnt4)
{
if (cnt3 >= 1) // 3+3+...
get(cnt, use + ksm[num] * 3, unuse - ksm[num] * 3);
dfs(cnt, pos + 1, use + ksm[num] * 3, unuse - ksm[num] * 3, cnt1, cnt2, cnt3 + 1, cnt4, num, pd && (last + 1 == num || last < 0));
// 尝试构成 3 顺
}
if (c >= 2 && !cnt1 && !cnt3 && !cnt4)
{
if (cnt2 >= 2) // 2+2+2+...
get(cnt, use + ksm[num] * 2, unuse - ksm[num] * 2);
dfs(cnt, pos + 1, use + ksm[num] * 2, unuse - ksm[num] * 2, cnt1, cnt2 + 1, cnt3, cnt4, num, pd && (last + 1 == num || last < 0));
// 尝试构成对顺
}
if (c >= 1 && !cnt2 && !cnt3 && !cnt4)
{
if (cnt1 >= 4) // 1+1+1+1+1+...
get(cnt, use + ksm[num] * 1, unuse - ksm[num] * 1);
dfs(cnt, pos + 1, use + ksm[num] * 1, unuse - ksm[num] * 1, cnt1 + 1, cnt2, cnt3, cnt4, num, pd && (last + 1 == num || last < 0));
// 尝试构成单顺
}
}
}
dfs(cnt, pos + 1, use, unuse, cnt1, cnt2, cnt3, cnt4, last, pd);
// 不出这个点数的牌
}
void solve()
{
// 注意清空 !!!!!
ans = 0;
ans_cnt = -1;
while (q.size()) q.pop();
mp.clear();
for (int i = 1; i <= n; i++)
{
a[i] = read();
if (a[i] == 1) a[i] += 13;
read();
}
sort(a + 1, a + 1 + n);
ll ansssss = 0;
for (int i = 1; i <= n; i++) ansssss += ksm[a[i]];
q.push({0, 0, ansssss});
mp[0] = 0;
if (n)
{
while (q.size())
{
int cnt = q.front().cnt + 1;
ll use = q.front().use, unuse = q.front().unuse, x = unuse;
if (ans && cnt != ans_cnt) break; // 搜到过且不可能有最优状态,则退出循环
if (mp.count(unuse)) // 剪枝
ans = (ans == 0 ? cnt - 1 + mp[unuse] : min(ans, cnt - 1 + mp[unuse])), ans_cnt = cnt - 1;
q.pop();
tail = 0;
memset(t, 0, sizeof(t));
// 记录每个点数还需要出多少张牌
for (int i = 0; i < 15; i++)
{
t[i] += x % 5;
x /= 5;
if (t[i]) st[++tail] = i;
}
dfs(cnt, 1, use, unuse, 0, 0, 0, 0, -114514, 1);
}
}
cout << ans << "\n";
}
signed main()
{
int T = read();
n = read();
while (T--) solve();
return 0;
}
以下是博客签名,正文无关
本文来自博客园,作者:Wy_x,转载请在文首注明原文链接:https://www.cnblogs.com/Wy-x/articles/19044442
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC-BY-NC-SA 4.0 协议)进行许可。

浙公网安备 33010602011771号