【BZOJ-2201】彩色圆环

题目描述

小 A 喜欢收集宝物。一天他得到了一个圆环,圆环上有 N 颗彩色宝石,闪闪
发光。小A很爱惜这个圆环,天天把它带在身边。
一天,小 A 突然发现圆环上宝石的颜色是会变化的。他十分惊讶,仔细观察这
个圆环后发现,圆环上宝石的颜色每天变化一次,而且每颗宝石的颜色都等概率地
为特定的M种颜色之一。小A发现了这个秘密后,对圆环更是爱不释手,时时刻刻都
在研究。
又经过了一段时间,小 A 发现因为圆环上宝石的颜色不断变化,圆环有时会显
得比其他时候更美丽。为了方便比较,小 A 这样定义圆环的“美观程度” :
1. 设圆环上相同颜色的宝石构成的连续段长度分别为a 1 , a 2 , …, a n ;
2. 定义圆环的“美观程度”
R = a 1 * a 2 * … * a n 。
现在小 A 想知道,在上述前提下,圆环的“美观程度”的期望值 E(R)是多少。
因为如果知道了 E(R),他就可以判断每天变化出的新圆环是否比一般情况更美丽。
说明:“美观程度”的期望值即为对每种可能的圆环状态的“美观程度”与其
出现概率的乘积进行求和所得的值。

题解

一道概率dp题。
先来看看是链的情况:
d p [ i ] [ j ] i j
但是发现这样子不好转移。当加入一个不与前面同色的宝石时不好搞。
那么我们其实可以先就确定哪一段是同色的。
d p [ i ] i j ( i + 1 ) j
i P [ i ]
那么就有 d p [ j ] + = d p [ i ] P [ j i ] ( j i 1 ) ( m 1 ) (注意这里一定写成+=,概率dp的期望都是要累加的)
但是为什么最后要乘上(m-1)而呢?
因为前面已经有一种颜色了,这里实际上你有(m-1)种颜色的选择,并且每一种颜色的地位相同,可以轮换。

那么链的情况就处理完了,再来看看环。
我们肯定是要破环成链的,关键看怎么处理首尾相连时的贡献。
既然每次只有当首尾颜色相同时才有贡献,那么不妨给状态增加一维:
d p [ i ] [ 2 ] i 0 1
但是我们还要计算贡献的大小,可以给状态再增一维表示第一段的长度,但其实不必要。
在是链的情况中,我们是枚举了同色段的长度,那么这里同理,可以枚举一下第一段有多长,每次重新计算。统计答案时再枚举一下最后那段和第一段同色的有多长即可。
转移的话,和链的情况类似,设当前位为j,枚举了一个后面的k表示(j+1)到k同色:
d p [ k ] [ 0 ] + = d p [ j ] [ 0 ] P [ k j ] ( k j ) ( M 2 ) + d p [ j ] [ 1 ] P [ k j ] ( k j ) ( M 1 )
d p [ k ] [ 1 ] + = d p [ j ] [ 0 ] P [ k j ] ( k j )
这里有一个乘的是(m-2),大家可自己想想为什么。

O ( n 3 )
代码如下

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
int n,m;
typedef long double ldb;
typedef long long ll;
const ldb eps=1e-18;
const int N=300;
ldb dp[N][2];
ldb P[N];
int main()
{
    scanf("%d %d",&n,&m);
    if(m==1){printf("%d\n",n);return 0;}
    register ldb PPP=1.0000000/(m*1.0000000);
    P[0]=1.00000;
    for(register int i=1;i<=n;i++){
        P[i]=P[i-1]*PPP+eps;
    }
    register ldb ans=0;
    register ldb M=1.0000*m;
    for(register int i=1;i<=n;i++){
        Set(dp,0);//每次都要清空
        dp[i][1]=1.000;//这里期望设成1,首尾那段最后统计
        if(i==n){
            ans+=1.00000*n*P[n]*M;
            continue;
        }
        for(register int j=i;j<=n;j++){
            for(register int k=j+1;k<=n;k++){
                dp[k][0]+=dp[j][0]*P[k-j]*(ldb)(k-j)*(M-2.00)+dp[j][1]*P[k-j]*(k-j)*(M-1.00);
                dp[k][1]+=dp[j][0]*P[k-j]*(ldb)(k-j);
            }
        }
        for(register int j=0;j<=n-i;j++){
            ans+=dp[n-j][0]*(P[j+i])*(i+j)*M;
        }
    }
    printf("%.9Lf\n",ans);
}

以为到这就结束了?
还没有呢!
d p d p i
仅仅只是换了个下标!
所以我们再来看看我们枚举的i有什么用。
实际就是,把前i个宝石砍掉不要先对后面的i+1到n个宝石求个期望再统计答案。
假设我们只dp一次,那么就只是统计答案时受到影响,
且此时 d p [ i ] [ ] i
所以我们如果枚举最后那一个同色段是无法统计答案的,所以我们考虑枚举首尾相连时同色段的总长度,设为 l
这时 d p [ n l ] [ 0 ] ( n l )
l l d p [ n l ] [ 0 ]
a n s + = i i d p [ n i ] [ 0 ] P [ i ] M ;

我们就将算法优化到了 O ( n 2 )
整个空间复杂度仍为 O ( n )

代码如下

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
int n,m;
typedef long double ldb;
typedef long long ll;
const ldb eps=1e-18;
const int N=300;
ldb dp[N][2];
ldb P[N];
int main()
{
    scanf("%d %d",&n,&m);
    if(m==1){printf("%d\n",n);return 0;}
    register ldb PPP=1.0000000/(m*1.00000000);
    P[0]=1.00000;
    for(register int i=1;i<=n;i++){
        P[i]=P[i-1]*PPP+eps;
    }
    register ldb ans=0;
    register ldb M=1.0000*m;
    dp[0][0]=1.000;
    for(register int i=0;i<=n;i++){
        for(register int j=i+1;j<=n;j++){
                dp[j][0]+=dp[i][0]*P[j-i]*(ldb)(j-i)*(M-2.00)+dp[i][1]*P[j-i]*(j-i)*(M-1.00);
                dp[j][1]+=dp[i][0]*P[j-i]*(ldb)(j-i);
            }
        }
    ans=P[n]*n*M;
    for(register int i=1;i<n;i++){
        ans+=i*i*dp[n-i][0]*P[i]*M;
    }
    printf("%.9Lf\n",ans);
}

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/79379780