【ybtoj】【期望dp】彩球抽取
题意
题目描述
袋子里有 \(n\) 个球。每次依次取出两个,把第二个球涂成第一个球的颜色,然后放回袋里搅匀。你的任务是算出让所有球的颜色相同所需要的取球次数期望。
输入格式
输入仅一行,包含一个长度小于 \(25\) 的字符串,每个字符为一个大写英文字母,代表一个球的颜色。
输出格式
输出仅一行,表示答案,保留 \(6\) 位小数。
样例
样例输入1
AB
样例输出1
1.000000
样例输入2
ZCZ
样例输出2
3.000000
样例输入3
AAABB
样例输出3
11.666667
数据范围
\(n \le 25\)
题解
正常考虑每个球每次取的状态很难,所以要转化思路。
设 \(dp(i,j,k)\) 表示前 \(i\) 轮,颜色为 \(j\) 的球有 \(k\) 个的期望步数。
那么答案就是 \(\sum\limits_{i=0}^{25} dp(i,j,n)\times i\).
这样就成功把不好表示的球颜色的状态表示了,因为状态变得只和当前所求颜色有关。
但是这样循环 \(i\) 的上界在哪里?要求保留 6 位小数,结合时间上限大概可以设上界为 \(50000~100000\)。(题解里说 \(6\) 位小数精度不是特别高)
关于 \(i\) 这一维,可以用滚宫数组优化。
PS:本人写的时候 \(n\) 在全局和主函数里分别定义了一遍,调吐血了...$
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
inline ll read()
{
ll ret=0;char ch=' ',c=getchar();
while(!(c>='0'&&c<='9')) ch=c,c=getchar();
while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
int n;
double dp[30][30][2];
int cnt[30],roll;
char s[30];
inline void clear()
{
for(int i=0;i<26;i++)
for(int j=0;j<=n;j++)
dp[i][j][roll]=0;
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++) cnt[s[i]-'A']++;
for(int i=0;i<26;i++) if(cnt[i]) dp[i][cnt[i]][0]=1;
double res=0;
for(int stp=0;stp<=50000;stp++)
{
//printf("###Stp=%d:\n",stp);
for(int i=0;i<26;i++)
{
//if(dp[i][n][roll]) printf("dp[%d][%d]=%.3lf\n",i,n,dp[i][n][roll]);
res+=stp*dp[i][n][roll];
}
//printf("res=%.3lf\n",res);
//printf("roll:%d\n",roll);
roll^=1;
clear();
for(int i=0;i<26;i++)
for(int j=1;j<n;j++)
{
double prob=1.0*j*(n-j)/n/(n-1);//同时选出一个i颜色和和一个非i颜色的球的可能性
//printf("prob=%.3lf\n",prob);
dp[i][j-1][roll]+=dp[i][j][roll^1]*prob/2;
dp[i][j+1][roll]+=dp[i][j][roll^1]*prob/2;
dp[i][j][roll]+=dp[i][j][roll^1]*(1-prob);
//printf("dp1=%.3lf,dp2=%.3lf,dp3=%.3lf\n",dp[i][j][roll],dp[i][j-1][roll],dp[i][j][roll]);
//printf("dp[%d][%d][%d]=%.3lf\n",i,j,roll^1,dp[i][j][roll^1]);
}
}
printf("%.6lf",res);
return 0;
}