题解 | #Chiitoitsu#
Chiitoitsu
https://ac.nowcoder.com/acm/contest/33186/I
题意
- 初始手牌有 13 张麻将牌,相同牌至多出现 2 张
- 所有的牌有34种,每种牌都有4张
- 每轮可以从牌堆摸牌,若达成七对子则自摸胡牌
- 若不然则选择手牌中某张牌并丢弃
- 给定初始手牌,求最优策略下达成七对子的期望轮数
- 多组数据,数据组数不超过 100000组
解析
由题意我们可以指,最优策略为:如果摸到的牌能和手上的牌凑成对子,则留下,否则丢掉,按照这样一个策略,我们可以使得手上没有成对的牌都是没有摸到过的牌,即牌堆中还有三张这样的牌
用dp[i] [j]表示手上还有i张单牌,牌堆中还有j张牌,还要模牌次数的期望值
那么每次摸牌可以分为两种情况: ①摸到的牌可以和手上的牌凑对,留下 其概率为:(i * 3) / j (因为手中有i张单牌,那么牌堆中每种单牌都有三张) ②摸到的牌不能和手上的牌凑对,丢掉 其概率为:(j - 3 * i) / j
接着,我们就可以知道状态转移方程了: dp[i] [j] = 1 + (3 * i) / j * dp[i - 2][j - 1] + (j - 3 * i) / j * dp[i] [j - 1]
但是我们要对dp[1] [i[进行初始化: dp[1][i] = 1 + (i - 3) / i * dp[1][i - 1] 因为当手上只有一张单牌的时候,不管有没有凑成对子,都得有这一回合,如果凑成对子了,后面就不用摸牌了;如果没有凑成对子,那就还需要dp[1][i - 1] 回合,其概率为(i - 3) / i
另外,由于本题需要取模,而期望值并不一定是整数,所以要用到逆元和快速幂,不会的可以去了解一下~
提醒一下,手中单牌的数量只会是1,3,5,……,13
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll p = 1e9 + 7;
int t;
ll dp[20][200];
ll ksm(ll a,ll k)
{
ll res = 1;
while(k){
if(k & 1) res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res;
}
int main()
{
for(int i = 3;i <= 123;i ++){
dp[1][i] = (1 + (((i - 3) * ksm(i,p - 2) % p) * dp[1][i-1] % p)) % p;
}
for(ll i = 3;i <= 13;i += 2){
for(ll j = 3;j <= 123;j ++){
dp[i][j] = (1 + (((i * 3) * ksm(j,p - 2) % p) * dp[i - 2][j - 1] % p) + (((j - i * 3) * ksm(j,p - 2) % p) * dp[i][j - 1] % p)) % p;
}
}
cin >> t;
for(int Case = 1;Case <= t;Case ++){
unordered_map<string,int> mp;
string s;
cin >> s;
int n = 0;
for(int i = 0;i < s.length();i += 2){
string tmp = "";
tmp += s[i];
tmp += s[i + 1];
mp[tmp] ++;
}
for(int i = 0;i < s.length();i += 2){
string tmp = "";
tmp += s[i];
tmp += s[i + 1];
if(mp[tmp] == 1) n ++;
}
cout << "Case #" << Case << ": " << dp[n][123] << '\n';
}
return 0;
}