题目相关
题目链接
AtCoder Beginner Contest 184 D 题,https://atcoder.jp/contests/abc184/tasks/abc184_d。
Problem Statement
We have a bag containing A gold coins, B silver coins, and C bronze coins.
Until the bag contains 100 coins of the same color, we will repeat the following operation:
Operation: Randomly take out one coin from the bag. (Every coin has an equal probability of being chosen.) Then, put back into the bag two coins of the same kind as the removed coin.
Find the expected value of the number of times the operation is done.
Input
Input is given from Standard Input in the following format:
a b c
Output
Print the expected value of the number of times the operation is done. Your output will be accepted if its absolute or relative error from the correct value is at most 10^−6.
Samples1
Sample Input 1
99 99 99
Sample Output 1
1.000000000
Explaination
No matter what coin we take out in the first operation, the bag will contain 100 coins of that kind.
Samples2
Sample Input 2
98 99 99
Sample Output 2
1.331081081
Explaination
We will do the second operation only if we take out a gold coin in the first operation. Thus, the expected number of operations is
Samples3
Sample Input 3
0 0 1
Sample Output 3
99.000000000
Samples4
Sample Input 4
31 41 59
Sample Output 4
91.835008202
Constraints
- 0≤A,B,C≤99
- A+B+C≥1
题解报告
题目翻译
一个包里包含 A 个金币、B 个银币、C 个铜币。在包里钱币满足相同颜色达到 100 之前,我们可以重复以下动作:随机选一种钱币,取出一枚,再放入相同颜色钱币两枚。找出完成这些操作的期望值。
题目分析
根据题目的意思,其实就是每次向包里随机加入一枚钱币,直到包里某种钱币数量达到 100。本题的核心是如何计算期望。本题属于标准的动态规划求期望问题。直接套用模板即可。
DP 数组定义
定义 DP[i][j][k],表示有 i 枚金币, j 枚银币, k 枚铜币的期望。
初值
所有的期望都为零。
递推方法
使用逆推。
转移方程
根据题目,每次增加一枚钱币,我们可以得到状态转移方程为:
AC 参考代码
//https://atcoder.jp/contests/abc184/tasks/abc184_d
//D - increment of coins
#include <bits/stdc++.h>
using namespace std;
//如果提交到OJ,不要定义 __LOCAL
#define __LOCAL
const int MAXN=1e2+2;
double dp[MAXN][MAXN][MAXN];
int main() {
#ifndef __LOCAL
//这部分代码需要提交到OJ,本地调试不使用
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
#endif
int a,b,c;
cin>>a>>b>>c;
for (int i=99; i>=a; i--) {
for (int j=99; j>=b; j--) {
for (int k=99; k>=c; k--) {
double t=i+j+k;
dp[i][j][k] = i/t*(dp[i+1][j][k]+1) + j/t*(dp[i][j+1][k]+1) + k/t*(dp[i][j][k+1]+1);
}
}
}
cout<<fixed<<setprecision(9)<<dp[a][b][c]<<"\n";
#ifdef __LOCAL
//这部分代码不需要提交到OJ,本地调试使用
system("pause");
#endif
return 0;
}
时间复杂度
。
空间复杂度
。
使用蒙特卡洛方法模拟
本题核心其实是一个随机模拟过程,因此也可以使用蒙特卡洛方法(Monte Carlo method)来模拟这个过程。首先就不证明这个过程是收敛的,说真的,我也不大会证明,以后努力。具体的 Monte Carlo method 请看相关资料。
因此,我们只需要模拟这样的过程,只需要足够的样本数量就可以完成模拟。我这里用了 300 次就可以满足题目的需求。为什么 300 就够了,其实就是反复测试出来的。
AC 参考代码
//https://atcoder.jp/contests/abc184/tasks/abc184_d
//D - increment of coins
//Using Monte Carlo method
#include <bits/stdc++.h>
using namespace std;
//如果提交到OJ,不要定义 __LOCAL
//#define __LOCAL
//计算概率
double prob(int n, int a, int b, int c) {
int u=100-a;
if (u>n) {
return 0.0;
}
if (n-u>(99-b)+(99-c)) {
return 0.0;
}
double p=1.0, ret=0.0;
for (int i=0; i<u; i++) {
p *= 1.0*(a+i)/(a+b+c+i);
}
for (int v=0; v<=n-u; v++) {
if (v+b>99 || n-u-v+c>99) {
continue;
}
double exp=1.0;
for (int j=0; j<v; j++) {
exp *= 1.0*(b+j)/(a+b+c+u+j);
}
for (int j=0; j<n-u-v; j++) {
exp *= 1.0*(c+j)/(a+b+c+u+v+j);
}
for (int j=v; j>=1; j--) {
exp *= 1.0*(u-1+j)/j;
}
for (int j=n-u-v; j>=1; j--) {
exp *= 1.0*(u-1+v+j)/j;
}
ret += (p*exp);
}
return ret;
}
int main() {
#ifndef __LOCAL
//这部分代码需要提交到OJ,本地调试不使用
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
#endif
int a,b,c;
cin>>a>>b>>c;
double ans=0;
for (int n=1; n<=300; n++) {
double pa=prob(n, a, b, c);
double pb=prob(n, b, c, a);
double pc=prob(n, c, a, b);
ans += n*(pa+pb+pc);
}
cout<<fixed<<setprecision(9)<<ans<<"\n";
#ifdef __LOCAL
//这部分代码不需要提交到OJ,本地调试使用
system("pause");
#endif
return 0;
}
时间复杂度
。虽然也是三重循环,我们要注意到循环的次数是常数项。比如 main() 种的循环是固定 300 次,prob() 中的循环最多是 100 次。所以乘积还是常数次。
空间复杂度
。