【概率与期望】【骗分】观光铁路

问题描述
  跳蚤国正在大力发展旅游业,每个城市都被打造成了旅游景点。
  许多跳蚤想去其他城市旅游,但是由于跳得比较慢,它们的愿望难以实现。这时,小C听说有一种叫做火车的交通工具,在铁路上跑得很快,便抓住了商机,创立了一家铁路公司,向跳蚤国王请示在每两个城市之间都修建铁路。
  然而,由于小C不会扳道岔,火车到一个城市以后只能保证不原路返回,而会随机等概率地驶向与这个城市有铁路连接的另外一个城市。
  跳蚤国王向广大居民征求意见,结果跳蚤们不太满意,因为这样修建铁路以后有可能只游览了3个城市(含出发的城市)以后就回来了,它们希望能多游览几个城市。于是跳蚤国王要求小C提供一个方案,使得每只跳蚤坐上火车后能多游览几个城市才回来。

小C提供了一种方案给跳蚤国王。跳蚤国王想知道这个方案中每个城市的居民旅游的期望时间(设火车经过每段铁路的时间都为1),请你来帮跳蚤国王。
输入格式
  输入的第一行包含两个正整数n、m,其中n表示城市的数量,m表示方案中的铁路条数。
  接下来m行,每行包含两个正整数u、v,表示方案中城市u和城市v之间有一条铁路。
  保证方案中无重边无自环,每两个城市之间都能经过铁路直接或间接到达,且火车由任意一条铁路到任意一个城市以后一定有路可走。
输出格式
  输出n行,第i行包含一个实数tBi,表示方案B中城市i的居民旅游的期望时间。你应当输出足够多的小数位数,以保证输出的值和真实值之间的绝对或相对误差不超过1e-9。


 是我错了,之前一直觉得蓝桥杯都是水题居多。这两天突然想刷一刷题库,然后就碰到了难题,这个就是其中一道比较变态的。
 首先我不是正解,但是网上一直查不到正解。能找到的只有这一篇:
  https://blog.csdn.net/weixin_40839812/article/details/79769757
 没有很搞明白,而且我觉得作者其实没考虑到不能返回的条件,又没有提供代码,因此不太确定正确性。以后慢慢研究。
 没有别的想法的话就先打打暴力,求期望先求概率,因为不能返回,所以某个时间下概率应该是二维的才能递推,以起点为1为例子,我们需要求出在第T步,从节点j到节点i的事件发生的概率 p b [ T ] [ i ] [ j ] pb[T][i][j] pb[T][i][j](其中i不等于起点1),这个好办,从T-1步按部就班推出第T步就行了,再找找第T步回到起点1的概率 ∑ p b [ T − 1 ] [ i ] [ j ] d [ i ] − 1 \sum{\frac{pb[T-1][i][j]}{d[i]-1}} d[i]1pb[T1][i][j](i与1联通而j不等于i),把这个数值乘上T,累加上答案就行了,只要枚举到足够大的T,答案就无限逼近与真实期望。
 出于精确性的考虑,有人会问,T如果越大,累加上的数值就会非常小吗?我没法子证明收敛速度,但是感觉上概率的减小的速度会远大于T增加的速度,因此收敛得还是比较快速的。
 于是尝试了第一次暴力,就是上面的思路

#include
#include
using namespace std;

double ans,pb[2][25][25];
vector<int> E[25];
int n,m,o;

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++)
		scanf("%d%d",&u,&v),E[u].push_back(v),E[v].push_back(u);
	for(int p=1;p<=n;p++)
	{
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				pb[0][i][j]=0;
		for(int q=0;q<E[p].size();q++)
			pb[0][E[p][q]][p]=1.0/E[p].size();
		ans=o=0;
		for(int t=1;t<=300;t++)  //这个t是T-1
		{
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
					pb[o^1][i][j]=0;
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
					for(int k=0;k<E[i].size();k++)
						if(E[i][k]!=j)
							if(E[i][k]!=p)
								pb[o^1][E[i][k]][i]+=pb[o][i][j]/(E[i].size()-1);
							else
								ans+=(t+1)*pb[o][i][j]/(E[i].size()-1);			
			o^=1;
		}
		printf("%.12lf\n",ans);
	}
	return 0;
}

 因为剩余的最差循环是 n 4 n^4 n4的效率,所以时间枚举到300就不行了。只得了5分,一个原因是精确度确实没我想象得高,另一个是因为蓝桥没有使用SPJ(我的天哪),也就是无形增加了题目难度,我需要让十二位小数完全一致。
 不过呢,说到骗分我还是极其在行的。首先,double统统搞成long double。其次如果n比较小,那么我们可以适当增大T,更精确。另外,直觉告诉我,T很大的时候,概率pb数组之间的相对大小可能会达到某种稳定状态,也就意味着每次回到起点的概率,在T较大的时候可能会是一个无穷等比递缩数列。经过程序验证,我的想法达到了证实。那么,如果得知这个数列的公比v,那么在枚举完之后,我用级数求和的方式去补足我少加的部分,精确度会大大提高。(忽然想到刘徽在求出3.14之后用类似的补足法搞出了3.1416 QwQ)
 假设我的t枚举到了 l i m lim lim(也就是T=lim+1),第lim步的增量为a,我用 l i m lim lim l i m − 1 lim-1 lim1两步的回到原点的概率比作为公比v,然后,我们要求的是 ∑ a ∗ v ∗ T + a ∗ v 2 ∗ ( T + 1 ) … … \sum{a*v*T+a*v^2*(T+1)}…… avT+av2(T+1),这个用错位相减法或者积分法来求,没啥大问题。
 经过实践发现效果出奇好。
 改进后的暴力:

#include
#include
using namespace std;

int lim;

long double ans,pb[2][25][25],ty,tx;
vector<int> E[25];
int n,m,o;

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++)
		scanf("%d%d",&u,&v),E[u].push_back(v),E[v].push_back(u);
	lim=60000000/n/n/n/n;
	for(int p=1;p<=n;p++)
	{
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				pb[0][i][j]=0;
		for(int q=0;q<E[p].size();q++)
			pb[0][E[p][q]][p]=1.0L/E[p].size();
		tx=ty=ans=o=0;
		for(int t=1;t<=lim;t++)
		{
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
					pb[o^1][i][j]=0;
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
					for(int k=0;k<E[i].size();k++)
						if(E[i][k]!=j)
							if(E[i][k]!=p)
								pb[o^1][E[i][k]][i]+=pb[o][i][j]/(E[i].size()-1);
							else
							{
								ans+=(t+1)*pb[o][i][j]/(E[i].size()-1);
								if(t==lim-1)
									tx+=pb[o][i][j]/(E[i].size()-1);
								else if(t==lim)
									ty+=pb[o][i][j]/(E[i].size()-1);
							}
			o^=1;	
		}
		long double v=0;
		int cnt=0;
		if(tx>0)
			v=ty/tx;
		printf("%.12Lf\n",ans+ty*(lim*v+(2*v-v*v)/(1-v))/(1-v));
	}
	return 0;
}

 拿了62分,没有办法再改进了,真不知道剩下的是什么奇怪的数据。个人觉得如果上SPJ还是有AC的机会的。就这样吧,要求憋太高了,另外期待正解。

你可能感兴趣的:(概率与期望)