HDU4624 Endless Spin(概率&&dp)

2013年多校的题目,那个时候不太懂怎么做,最近重新拾起来,看了一下出题人当初的解题报告,再结合一下各种情况的理解,终于知道整个大致的做法,这里具体写一下做法。

题意:给你一段长度为[1..n]的白色区间,每次随机的取一个子区间将这个区间涂黑,问整个区间被涂黑时需要的期望次数。

1. 首先要做的是一个题目的转化。如果我定义pi为 恰好i次将区间涂黑的概率,那么显然期望 E= 0*p0+1*p1+2*p2+... 换一种角度看这个公式,其实这个公式可以这么写

        E = p1 + p2 + p3 + p4 + p5 + ...

                  p2 + p3 + p4 + p5 + ...

                   p3 + p4 + p5 + ...

定义Li=p(i+1) + p(i+2) + ... 那么我们可以把Li理解成覆盖了i次都没有将整个区间涂黑的概率。

所以E=L0+L1+L2+...

2. 理解了上面这一点之后我们可以先考虑一种暴力的做法,那就是枚举2^n个最后留下来的点,譬如最后留下来的点是v1,v2,v3...vk,那么实际上可选的区间只有[1,v1-1],[v1+1,v2-1],[v2+1,v3-1]...那么这里区间的选法就有A种(A是可以求出来的),要使这些点都能被留下来,那么我们每次选的只能是这A个区间,因而概率 p = A/ ((n+1)*n/2). 那么对于Li来说 Li += p^i* (-1)^(k-1).  之所以要乘(-1)^k是因为这里需要有一个容斥。考虑到p^i的累加和即 p+p^1+p^2+... = 1/(1-p) 所以对于每个2^n,它对最后期望的影响实际上是 1/(1-p)*(-1)^(k-1).

3. 暴力的想法给到了,那么下面就是要优化一下这种做法了,因为我总不可能2^50枚举所有剩下的点。一个自然的想法是考虑dp,看上面的式子我们不难发现实际上最后起作用的是 A和k的奇偶性,而A的范围是有限的(总的区间个数),那么我们可以定义一个状态dp[i][j][k]表示,第i个点是白点(未被覆盖),且前i个白点的奇偶性为j,可选区间的个数为k有多少个子集。 那么对于一个期望值E[n]来说(n>=i) 这个状态对E[n]的影响实际上是

可选的区间  A = 前i个点的可选区间数+ [i...n]这一段为黑的可选区间数 = k+(n-i+1)(n-i)/2;

      p = A/((n+1)*n/2);

      E[n] += dp[i][j][k]*(1-p)*(-1)^(j-1);

dp状态的转移具体可以参考一下下面的代码。

4. 题目的第二个坑点就在于它需要有15个小数点的精度,所以需要写一个高精度类,这里只需要涉及两个浮点数的加法和减法就可以了,所以写的时候还是挺容易的,注意答案是round到15个小数点,所以如果小数点后第16个数是>=5的,输出之前还要将结果加上一个1e-15.

下面贴两分代码,一份是不需要高精度的时候的dp的代码,另外一份是要高精度的代码。

#pragma warning(disable:4996)
#include<algorithm>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<queue>
#include<list>
#include<time.h>
#include<bitset>
using namespace std;

#define ll long long
#define maxn 55

ll dp[maxn][2][maxn*(maxn + 1) / 2];
double E[maxn];

int main()
{
	memset(dp, 0, sizeof(dp));
	memset(E, 0, sizeof(E));
	dp[0][0][0] = 1;
	for (int i = 0; i <= 50; ++i){
		for (int j = 0; j <= (i + 1)*i / 2; ++j){
			if (dp[i][0][j]) {
				for (int k = i + 1; k <= 50; ++k){
					dp[k][1][j + (k - i)*(k - i - 1) / 2] += dp[i][0][j];
				}
			}
			if (dp[i][1][j]){
				for (int k = i + 1; k <= 50; ++k){
					dp[k][0][j + (k - i)*(k - i-1) / 2] += dp[i][1][j];
				}
			}
		}
	}

	for (int i = 1; i <= 50; ++i){
		for (int k = 0; k <= i; ++k){
			for (int j = 0; j <= (k + 1)*k / 2; ++j){
				if (dp[k][0][j]){
					long double p = (j + (i - k + 1)*(i - k) / 2 + .0) / ((i + 1)*i / 2);
					if (p == 1.0) { continue; }
					E[i] -= dp[k][0][j] / (1 - p);
				}
				if (dp[k][1][j]){
					long double p = (j + (i - k + 1)*(i - k) / 2 + .0) / ((i + 1)*i / 2);
					if (p == 1.0) { continue; }
					E[i] += dp[k][1][j] / (1 - p);
				}
			}
		}
	}
	int xx;
	cin >> xx;
}

  

#pragma warning(disable:4996)
#include<algorithm>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<queue>
#include<list>
#include<time.h>
#include<bitset>
using namespace std;

#define ll long long
#define maxn 55
#define maxdp 50

struct BigDecimal
{
	ll ip;
	int dp[maxdp];
	BigDecimal(){
		ip = 0;
		memset(dp, 0, sizeof(dp));
	}
	BigDecimal(int x){
		ip = x;
		memset(dp, 0, sizeof(dp));
	}
	BigDecimal(ll x, ll y)
	{
		ip = x / y;
		x = abs(x); y = abs(y);
		x = x%y;
		for (int i = 0; i < maxdp; ++i){
			x *= 10;
			dp[i] = x / y;
			x %= y;
		}
	}
};

BigDecimal operator + (const BigDecimal &a, const BigDecimal &b)
{
	BigDecimal ret;
	int carry = 0;
	for (int i = maxdp - 1; i >= 0; --i){
		ret.dp[i] = (a.dp[i] + b.dp[i] + carry) % 10;
		carry = (a.dp[i] + b.dp[i] + carry) / 10;
	}
	ret.ip = a.ip + b.ip + carry;
	return ret;
}

BigDecimal operator - (const BigDecimal &a, const BigDecimal &b)
{
	BigDecimal ret;
	int marry = 0;
	for (int i = maxdp - 1; i >= 0; --i){
		ret.dp[i] = (a.dp[i] - b.dp[i] - marry + 10) % 10;
		marry = a.dp[i] - b.dp[i] - marry < 0 ? 1 : 0;
	}
	ret.ip = a.ip - b.ip - marry;
	return ret;
}

void print(const BigDecimal& x, int digits)
{
	printf("%I64d.", x.ip);
	BigDecimal y;
	if (x.dp[digits] >= 5){
		y.dp[digits - 1] = 1;
	}
	y = x + y;
	for (int i = 0; i < digits; ++i){
		printf("%d", y.dp[i]);
	}
	puts("");
}

ll dp[maxn][2][maxn*(maxn + 1) / 2];
BigDecimal Ep[maxn];
BigDecimal En[maxn];
BigDecimal E[maxn];

int main()
{
	memset(dp, 0, sizeof(dp));
	memset(E, 0, sizeof(E));
	dp[0][0][0] = 1;
	for (int i = 0; i <= 50; ++i){
		for (int j = 0; j <= (i + 1)*i / 2; ++j){
			if (dp[i][0][j]) {
				for (int k = i + 1; k <= 50; ++k){
					dp[k][1][j + (k - i)*(k - i - 1) / 2] += dp[i][0][j];
				}
			}
			if (dp[i][1][j]){
				for (int k = i + 1; k <= 50; ++k){
					dp[k][0][j + (k - i)*(k - i - 1) / 2] += dp[i][1][j];
				}
			}
		}
	}

	for (int i = 1; i <= 50; ++i){
		ll all = ((i + 1)*i / 2);
		for (int k = 0; k <= i; ++k){
			for (int j = 0; j <= (k + 1)*k / 2; ++j){
				if (dp[k][0][j]){
					ll select = j + (i - k + 1)*(i - k) / 2;
					if (select == all) continue;
					En[i] = En[i] + BigDecimal(all*dp[k][0][j], all - select);
				}
				if (dp[k][1][j]){
					ll select = j + (i - k + 1)*(i - k) / 2;
					if (select == all) continue;
					Ep[i] = Ep[i] + BigDecimal(all*dp[k][1][j], all - select);
				}
			}
		}
		E[i] = Ep[i] - En[i];
	}
	int T; cin >> T; int n;
	while (T--)
	{
		cin >> n;
		print(E[n], 15);
	}
	return 0;
}

  

时间: 2024-10-11 23:30:58

HDU4624 Endless Spin(概率&&dp)的相关文章

HDU4624 Endless Spin 【最大最小反演】【期望DP】

题目分析: 题目是求$E(MAX_{i=1}^n(ai))$, 它等于$E(\sum_{s \subset S}{(-1)^{|s|-1}*min(s))} = \sum_{s \subset S}{(-1)^{|s|-1}*E(min(s))}$. 那么设计期望DP,令$f[i][j][k]$表示前i个球,可选的区间为j个,球的个数是奇数还是偶数.然后就是要写一个高精度,不一定要真的写,可以yy出一种简便方法. 代码: 1 #include<bits/stdc++.h> 2 using na

hdu4624 Endless Spin (min-max容斥+dp)

min-max容斥: $$max\{a_i\}=\sum\limits_{S}(-1)^{|s|-1}min\{a_i|a_i \in S\}$$ 关于证明,可以把一个数$a$看作是集合$\{1...a\}$,于是max相当于取并集,min相当于取交集,就变成了普通的容斥 然后这道题就可以dp了 然而我一直被卡精度 以下代码大概是对的( 1 #include<bits/stdc++.h> 2 #include<tr1/unordered_map> 3 #define CLR(a,x

Codeforces 28C [概率DP]

/* 大连热身D题 题意: 有n个人,m个浴室每个浴室有ai个喷头,每个人等概率得选择一个浴室. 每个浴室的人都在喷头前边排队,而且每个浴室内保证大家都尽可能均匀得在喷头后边排队. 求所有浴室中最长队伍的期望. 思路: 概率dp dp[i][j][k]代表前i个浴室有j个人最长队伍是k的概率. 枚举第i个浴室的人数.然后转移的时候其实是一个二项分布. */ #include<bits/stdc++.h> using namespace std; int jilu[55]; double dp[

hdu 3076 ssworld VS DDD (概率dp)

///题意: /// A,B掷骰子,对于每一次点数大者胜,平为和,A先胜了m次A赢,B先胜了n次B赢. ///p1表示a赢,p2表示b赢,p=1-p1-p2表示平局 ///a赢得概率 比一次p1 两次p0*p1 三次 p0^2*p1,即A赢的概率为p1+p*p1+p^2*p1+...p^n*p1,n->无穷 ///即a_win=p1/(1-p);b_win=p2/(1-p); ///dp[i][j]表示a赢了j次,b赢了i次的概率 ///dp[i][j]=dp[i-1][j]*b_win+dp[

hdu 3853 概率DP 简单

http://acm.hdu.edu.cn/showproblem.php?pid=3853 题意:有R*C个格子,一个家伙要从(0,0)走到(R-1,C-1) 每次只有三次方向,分别是不动,向下,向右,告诉你这三个方向的概率,以及每走一步需要耗费两个能量,问你走到终点所需要耗费能量的数学期望: 回头再推次,思想跟以前的做过的类似 注意点:分母为0的处理 #include <cstdio> #include <cstring> #include <algorithm>

hdu4089(公式推导)概率dp

题意:有n人都是仙剑5的fans,现在要在官网上激活游戏,n个人排成一个队列(其中主角Tomato最初排名为m), 对于队列中的第一个人,在激活的时候有以下五种情况: 1.激活失败:留在队列中继续等待下一次激活(概率p1) 2.失去连接:激活失败,并且出队列然后排到队列的尾部(概率p2) 3.激活成功:出队列(概率p3) 4.服务器瘫:服务器停止服务了,所有人都无法激活了(概率p4) 求服务器瘫痪并且此时Tomato的排名<=k的概率. 解法:ans[i][j]表示i个人出于第j个位置要到目的状

poj3071(概率DP)

题意:淘汰赛制,2^n(n<=7)个队员.给出相互PK的输赢概率矩阵.问谁最有可能赢到最后. 解法:ans[i][j]表示第i个队员第j轮胜出的概率.赢到最后需要进行n场比赛.算出每个人赢到最后的ans[i][n].写出序号的二进制发现一个规律,两个队员i.j如果碰到,那么一定是在第get(i,j)场比赛碰到的.get(i,j)计算的是i和j二进制不同的最高位,这个规律也比较明显. 代码: /****************************************************

【Foreign】开锁 [概率DP]

开锁 Time Limit: 10 Sec  Memory Limit: 256 MB Description Input Output Sample Input 4 5 1 2 5 4 3 1 5 2 2 5 4 3 1 5 3 2 5 4 3 1 5 4 2 5 4 3 1 Sample Output 0.000000000 0.600000000 0.900000000 1.000000000 HINT Main idea 一个宝箱内有一个可以开启别的宝箱的钥匙,可以选择k个宝箱,询问能开

POJ 2151 Check the difficulty of problems (概率DP)

题意:ACM比赛中,共M道题,T个队,pij表示第i队解出第j题的概率 ,求每队至少解出一题且冠军队至少解出N道题的概率. 析:概率DP,dp[i][j][k] 表示第 i 个队伍,前 j 个题,解出 k 个题的概率,sum[i][j] 表示第 i 个队伍,做出 1-j 个题的概率,ans1等于, T个队伍,至少解出一个题的概率,ans2 表示T个队伍,至少解出一个题,但不超过N-1个题的概率,最后用ans1-ans2即可. 代码如下: #pragma comment(linker, "/STA