版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Q1410136042/article/details/81285948
题目:看题目点这儿
题意:一个口袋中有n个球,球的颜色有c种。现从口袋中取出一个球。若取出的球与桌上已有球颜色相同,则将两球都取走,否则将取出的球放在桌上。设从口袋中取出每种颜色的球的概率均等。求取出n个球后桌面上剩余m个球的概率。n,m<=1e6,c<=100
题解:首先是个简单dp,假设表示取i个球后,桌上有j个球。这就意味着:
这个状态转移方程还是比较好理解的,首先dp[i]肯定得从dp[i-1]转移过来,而取的第i个球有两种情况:
- 第i个球的颜色在桌上没有,这样桌上球会增加一个,即桌上原本应该有(j-1)个球,那么这种情况概率为
- 第i个球的颜色在桌上已经有了,那么这两个球都会拿走,即桌上原本应该有(j+1)个球,那么这种情况概率为
这个DP很简单,但是这题有一个问题,n可能会达到1e6,也就是说dp的第一维最大可达到1e6,而第二维最大为c最大也能到100,即使不考虑时间问题,空间也是不够的。
那么,这一题应该如何优化呢?
这就涉及马尔可夫收敛定理,即:
如果满足下列四个条件,一个马尔科夫过程将收敛到一个均衡状态,且此均衡唯一:
- 可能的状态数量是有限的。
- 转移概率固定不变。
- 从任意一个状态能够变到任意其他一个状态。有可能不是从状态A直接变到状态C,而是先变到状态B再变到C,但只要有路径从状态A变成状态C就行。
- 过程不是简单循环。比如不能是从全A变到全B,然后又自动从全B变到全A。
简单地说,就是满足以上四个条件的过程称为马尔可夫过程,而马尔可夫过程往后面一直进行的时候,每一个状态都会自我收敛到均衡状态。
在这一题里就是说,当n足够大的时候,相同状态的概率都一样了。当然还需要注意一点,这一题还要分奇偶状态,因为每次取球,桌上的球要么多一个,要么少一个,也就是第奇数次取球,桌上的球一定为奇数,第偶数次取球,桌上的球一定为偶数,也就是说这一题关于奇偶分别收敛。
至于到n多大就会收敛,这我不会算,所以,我根据给定的时间和空间,假设到10000收敛,也就是当n>10000时,若n为奇数,令n=10001,若n为偶数,令n=10000,然后AC了。看有人说800以上就可以收敛,甚至有人用500都能AC。
AC代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#include<vector>
#include<iomanip>
#include<algorithm>
#include<string>
#include<cstring>
#include<ctime>
#include<queue>
#define ll long long
#define DEBUG printf("DEBUG\n")
#define FOR(i, s, n) for(int i = s; i < n; ++ i)
#define For(i, s, n) for(int i = s; i > n; -- i)
#define mem(a, n) memset((a), (n), sizeof(a))
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
const int mod = 1e9+7;
const int maxn = 1e5+10;
using namespace std;
typedef vector<int> V;
typedef vector<V> VV;
long double dp[10010][110];
int main()
{
#ifdef AFei
freopen("in.c", "r", stdin);
#endif // AFei
int c, n, m;
while(scanf("%d", &c), c)
{
scanf("%d%d", &n, &m);
if(m > c || (n&1) != (m&1))
{
printf("0.000\n");
continue;
}
memset(dp, 0, sizeof dp);
if(n > 10000) n = (n&1) + 10000;
dp[0][0] = 1.0;
FOR(i, 1, n+1)
FOR(j, 0, c+1)
{
if(j) dp[i][j] += dp[i-1][j-1]*(c-j+1)/c;
if(j < c) dp[i][j] += dp[i-1][j+1]*(j+1)/c;
}
printf("%.3Lf\n", dp[n][m]);
}
return 0;
}