UVALive-2522 Chocolate题解(马尔科夫收敛+简单DP)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Q1410136042/article/details/81285948

题目:看题目点这儿

题意:一个口袋中有n个球,球的颜色有c种。现从口袋中取出一个球。若取出的球与桌上已有球颜色相同,则将两球都取走,否则将取出的球放在桌上。设从口袋中取出每种颜色的球的概率均等。求取出n个球后桌面上剩余m个球的概率。n,m<=1e6,c<=100

题解:首先是个简单dp,假设dp[i][j]表示取i个球后,桌上有j个球。这就意味着:

dp[i][j]=dp[i-1][j-1] * [c-(j-1)]/c + dp[i-1][j+1]*(j+1)/c

这个状态转移方程还是比较好理解的,首先dp[i]肯定得从dp[i-1]转移过来,而取的第i个球有两种情况:

  1. 第i个球的颜色在桌上没有,这样桌上球会增加一个,即桌上原本应该有(j-1)个球,那么这种情况概率为[c-(j-1)]/c
  2. 第i个球的颜色在桌上已经有了,那么这两个球都会拿走,即桌上原本应该有(j+1)个球,那么这种情况概率为(j+1)/c

这个DP很简单,但是这题有一个问题,n可能会达到1e6,也就是说dp的第一维最大可达到1e6,而第二维最大为c最大也能到100,即使不考虑时间问题,空间也是不够的。

那么,这一题应该如何优化呢?

这就涉及马尔可夫收敛定理,即:

如果满足下列四个条件,一个马尔科夫过程将收敛到一个均衡状态,且此均衡唯一:

  1. 可能的状态数量是有限的。
  2. 转移概率固定不变。
  3. 从任意一个状态能够变到任意其他一个状态。有可能不是从状态A直接变到状态C,而是先变到状态B再变到C,但只要有路径从状态A变成状态C就行。
  4. 过程不是简单循环。比如不能是从全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;
}

猜你喜欢

转载自blog.csdn.net/Q1410136042/article/details/81285948