问题描述
跳蚤国正在大力发展旅游业,每个城市都被打造成了旅游景点。
许多跳蚤想去其他城市旅游,但是由于跳得比较慢,它们的愿望难以实现。这时,小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[T−1][i][j](i与1联通而j不等于i),把这个数值乘上T,累加上答案就行了,只要枚举到足够大的T,答案就无限逼近与真实期望。
出于精确性的考虑,有人会问,T如果越大,累加上的数值就会非常小吗?我没法子证明收敛速度,但是感觉上概率的减小的速度会远大于T增加的速度,因此收敛得还是比较快速的。
于是尝试了第一次暴力,就是上面的思路
#include<cstdio>
#include<vector>
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
lim−1两步的回到原点的概率比作为公比v,然后,我们要求的是
∑
a
∗
v
∗
T
+
a
∗
v
2
∗
(
T
+
1
)
…
…
\sum{a*v*T+a*v^2*(T+1)}……
∑a∗v∗T+a∗v2∗(T+1)……,这个用错位相减法或者积分法来求,没啥大问题。
经过实践发现效果出奇好。
改进后的暴力:
#include<cstdio>
#include<vector>
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的机会的。就这样吧,要求憋太高了,另外期待正解。