小H是一个程序员。但是他很喜欢一些新奇的东西。
有一次,他去找物理实验室的朋友玩。他见到了一串非常有意思的粒子。N个粒子排成一排。每一秒中,每一段连续的粒子中会随意有一个爆炸,爆炸后该粒子就消失了,且将原来连续的一段粒子分隔成两段。
小H希望知道所有粒子都爆炸完的期望时间。
Input
第一行为一个整数T(1 <= T<= 400),表示有T组测试数据;
每组数据一个正整数N(1<=N<=400),表示一开始的粒子数。
Output
对于每组数据,输出期望时间(秒)。保留五位小数。
解释一下,给定长度为i的串,记为L。随机的选择点j作为分裂点,因为是随机选择的,所以选点j的概率为1/i。这样,原串变成了两个字串,记为L1和L2,其长度风别为j-1和i-j。要求算原串爆炸时间为k的概率,有三种情况,第一,L1的串爆时间小于k-1,L2的爆照时间为k-1;第二,L1的串爆时间等于k-1,L2的爆照时间小于k-1;第三,L1和L3的爆照时间都为k-1。使用全概率公式把上面三种情况加起来。
时间空间分析:观察上面的公式,有i,k,j,sum四个变量,时间复杂度为O(N^4),为了降低时间复杂度,可把sum的部分存放起来,这样时间复杂度为O(N^3),空间复杂度为O(N^2).
我的实现
#include <iostream>
#include <fstream>
#include <string>
#define NUPPER 400
using namespace std;
// 使用动态规划
double aver_bob_time(int N){
double re;
float tran[NUPPER + 1][NUPPER + 1];
float tran_sum[NUPPER + 1][NUPPER + 1];
for(int i = 0; i <= N; i++)
for(int j = 0; j <= N; j++){
tran[i][j] = 0;
tran_sum[i][j] = 0;
}
for(int i = 0; i <= N; i++)
for(int j =i; j <= N;j++)
tran_sum[i][j] = 1;
tran[0][0] = 1;
for(int i = 1; i <= N; i++){
for(int k = 1; k <= i; k++){
for(int j = 1; j <= i; j++){
int lr = j - 1;
int ll = i - j;
if(min(lr,k - 2) >= 0)
tran[i][k] += tran_sum[lr][min(lr,k - 2)] * tran[ll][k - 1];
if(min(ll,k - 2) >=0 )
tran[i][k] += tran_sum[ll][min(ll,k - 2)] * tran[lr][k - 1];
tran[i][k] += tran[lr][k -1] * tran[ll][k -1];
}
tran[i][k] /= i;
tran_sum[i][k] = tran_sum[i][k - 1] + tran[i][k];
}
}
re = 0;
int tmp = 0;
for(int i = 1; i <= N; i++){
re += i * tran[N][i];
}
return re;
}
int main(int argc, char* argv[]){
int N;
while(cin >> N){
cout << aver_bob_time(N) << endl;
}
return 0;
}