Chiitoitsu(期望DP)
题意
总共有种麻将牌,每种牌有张。初始手牌有张牌,相同牌至多出现张。
每轮可以从牌堆摸牌,若达成七对子则自摸胡牌,若不然则选择手牌中某张牌并丢弃之。
给定初始手牌,求最优策略下达成七对子的期望轮数。
(题目翻译来源于雨巨的讲解PPT)
题目链接:https://ac.nowcoder.com/acm/contest/33186/I
数据范围
多组数据
思路
期望DP,典型套路是定义一个转移数组,表示从该状态到最终状态的期望轮数。
在这里,必须要有的一个状态是当前手里的对子数(或者是单牌数),为了能够计算概率,因此还需要知道牌堆里面还有多少牌,因此另外一个状态表示牌堆中还剩多少牌。
因此,我们用表示牌堆中还有张牌,并且手里有个对子时,还需要多少次摸牌才能够胡牌的期望。
在考虑递推式之前,先分析一下最优策略,即我们应该丢弃什么牌。显然,我们应该丢弃单牌,一旦成对了就不再丢弃。因此,我们可以推断,自己手牌中的单牌,在牌堆中一定有张,因为只要从牌堆中再拿一张,那么就可以凑成对子了。
现在考虑递推式。,第一项表示摸到一张新的单牌,第二项表示可以和已有的单牌凑对。
由于容易计算,先算。,其中表示手上单牌种类。
记忆化搜索即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
typedef long long ll;
const int N = 150, M = 10, mod = 1e9 + 7;
char s[N];
map<string, int> mp;
ll f[N][M];
ll qmi(ll a, ll b)
{
ll res = 1;
while(b) {
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll inv(ll x)
{
return qmi(x, mod - 2);
}
ll dfs(int a, int b)
{
ll &v = f[a][b];
if(v >= 0) return v;
ll t = a - 3 * (13 - 2 * b);
if(t > 0) {
ll tmp = inv(a);
ll p1 = t * tmp % mod, p2 = 3 * (13 - 2 * b) * tmp % mod;
v = (1 + p1 * dfs(a - 1, b) + p2 * dfs(a - 1, b + 1)) % mod;
}
else {
v = (1 + dfs(a - 1, b + 1)) % mod;
}
return v;
}
int main()
{
int T;
scanf("%d", &T);
memset(f, -1, sizeof f);
for(int i = 0; i < N; i ++) f[i][7] = 0;
for(int cas = 1; cas <= T; cas ++) {
scanf("%s", s + 1);
mp.clear();
for(int i = 1; i <= 13; i ++) {
string t = "";
t += s[2 * i - 1], t += s[2 * i];
mp[t] ++;
}
int cnt = 0;
for(auto p : mp) {
if(p.second >= 2) cnt ++;
}
printf("Case #%d: %lld\n", cas, dfs(123, cnt));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话