这道题,额,反正我是刚了2天,然后就萎了。。。。。。(是不是觉得我很菜)

题目描述:

放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩。

进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点、m条道路的无向连通图,且该图中至多有一个环(即m只可能等于n或者n-1)。小Z现在所在的大门也正好是一个景点。小Z不知道什么好玩,于是他决定,从当前位置出发,每次随机去一个和当前景点有道路相连的景点,并且同一个景点不去两次(包括起始景点)。贪玩的小Z会一直游玩,直到当前景点的相邻景点都已经访问过为止。

小Z所有经过的景点按顺序构成一条非重复路径,他想知道这条路径的期望长度是多少?

小Z把游乐园的抽象地图画下来带回了家,可是忘了标哪个点是大门,他只好假设每个景点都可能是大门(即每个景点作为起始点的概率是一样的)。同时,他每次在选择下一个景点时会等概率地随机选择一个还没去过的相邻景点。

【评分方法】本题没有部分分,你程序的输出只有和标准答案的差距不超过0.01时,才能获得该测试点的满分,否则不得分。

数据范围:n<=100000,环中节点个数小于20,。

思路分析:

这题,怎么说呢?额,很恶心,嗯,没错,就是这样。

前两天,我没看题解,自己做,想了1个钟头思路就想出来了,但是代码调了两天还是错,第三天一看题解,发现需要注意的细节实在太多了,恶心的一批,调得我想杀人。。。

先将一下大体思路吧。

环中节点数小于20,很明显了,就是写一个基环树DP嘛,既然这样的话,我们就先来说一下一颗树的怎么写吧。

对于树上的一个节点,以它为出发点显然要分两种情况讨论:

1、往它的子树走(记作down操作)

2、往它的父亲走(记作up操作)

很明显可以用换根DP的方式来求解。状态:f[u]表示以u为起点,往u的子树走的期望长度,代码中的期望是没有算当前u节点影响的,其实你们也可以挑战一下直接表示期望的状态,反正我是挑战失败了,分类讨论写了一大堆,愣是没调出来。。。。。。先给大家说一说我为什么直接算期望难写吧,随便给大家举几个例子:

1、算f[u]要除以du[u],而路径上的其他点都只要除以du[u]-1就够了(一条边是由父亲走过来的,所以不用算)。

2、du[u]-1=0,u是否在环上,在的话du[u]要-2。

3、做环的时候,出度要在du[u]-2,du[u]-1,du[u]之间来回变化,还要根据是否有子树来判断是否应该停止。

4、......

然后,我差点把编译器卸了。。。。。。

好,接着我们刚才的状态,转移很简单:

f[u]=[Σv是u的儿子 (f[v]+dist[i])]/(du[u]-1)          若u为根则不用-1。(这个转移是直接表示期望)

d[u]=Σv是u的儿子 (f[v]+dist[i])                                                         (这是没算u的,代码里d用了p代替,但最终的dp数组还是d)

那么以v为起点往上走应该怎么做呢?

考虑换根,我们先把以u(v的父亲)为起点的期望路径长度减去儿子v对u的贡献,然后把u剩下的部分当做v的一个子树,算出u对于v的贡献。

转移:d[v]=(d[u]-f[v]-dist[i])/max(1,du[u]-1)+dist[i]

重点来了,注意方程中的du[u]-1,这是我再第二天才发现并想通的,当时满怀希望地交了一发,结果成功的得到了50分的好成绩,拿到了“再写N个讨论还是WA”的奖励。。。

为什么是du[u]-1呢,不是以v为起点吗,那不应该是du[v]吗,怎么会是du[u]-1呢?

想想我们对于dp的定义,看看我们是从哪里转移过来的,定义告诉我们,u对于d[u]的影响我们是还没有算的,而v对于d[v]的影响我们是不用算的(会在最后统计答案时统一除去),那么很清楚这个我们要算的是u对于d[v]的影响,而不是v对于d[v]的影响,所以便是du[u],而后面的那个-1就是因为u是从v走过来的,所以要除去u与v之间的连边。

在一颗树上的思路就是这样啦,很恶心不是吗?

那么恭喜你,当你处理完这么细节后,基环树上的dp过程中还有几个恶心的细节等着你去发现与处理。

先讲一下基环树dp的大体思路吧,对于环上的每一个点,都先对其对应子树跑一遍down操作。然后根据这些,算出每一个环上的点以它为起点的期望路径长度(也就是d[i])。再对环上的每一个点,对其对应子树跑一遍up操作。

思路很简单,——开始处理细节吧,少年!!!

我们剩下要处理的便是如何根据这些算出每一个环上的点以它为起点的期望路径长度了。

以u为起点,遍历环中节点。

若点v是已经转完了大半圈,下一个访问的节点就是u了,那么du[v]就要-2,因为v之前的一个点不能走,下一个点u也不能走。若v不属于刚才那种情况,那么du[v]就-1,因为之前的那个点不能走。g[u]=[∑v与u相连,且未被访问  (g[v]+dist[i])]/(du[u]-1或2)   注意如果节点v属于第1种情况则不能加dist[i]。

呼——,终于写完了!

写这篇题解的时候其实我一直都忘不了一开始被恶心细节统治的恐惧。。。。。。

代码实现:

var
f,d,g,p:array[1..100000]of double;
vis,cir:array[1..100000]of boolean;
next,dist,vet:array[1..200000]of longint;
head,du,fa:array[1..100000]of longint;
i,n,m,x,y,z,tot,root:longint;
ans:double;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure add(x,y,z:longint);
begin
inc(tot);
next[tot]:=head[x];
vet[tot]:=y;
head[x]:=tot;
dist[tot]:=z;
end;
procedure circle(u,father:longint);
var
i,v:longint;
begin
i:=head[u]; vis[u]:=true;
while i<>0 do
begin
v:=vet[i];
if (fa[u]<>v)and(vis[v]) then
begin
x:=u; fa[v]:=u;
while not cir[x] do
begin cir[x]:=true; x:=fa[x]; end;
end;
if not vis[v] then
begin fa[v]:=u; circle(v,u); end;
i:=next[i];
end;
end;
procedure down(u:longint);
var
i,v:longint;
begin
i:=head[u]; vis[u]:=true;
while i<>0 do
begin
v:=vet[i];
if (not vis[v])and(not cir[v]) then
begin
down(v);
p[u]:=p[u]+f[v]+dist[i];
inc(du[u]);
end;
i:=next[i];
end;
if du[u]>0 then f[u]:=p[u]/du[u];
if u<>root then inc(du[u]);
end;
procedure up(u,father:longint);
var
i,v:longint;
begin
i:=head[u];
while i<>0 do
begin
v:=vet[i];
if (v<>father)and(not cir[v]) then
begin
d[v]:=d[v]+(d[u]-f[v]-dist[i])/max(1,du[u]-1)+dist[i]; //du[u]-1要吃透
up(v,u);
end;
i:=next[i];
end;
end;
procedure dfs(u,father:longint);
var
i,v,k:longint;
flag:boolean;
begin
i:=head[u]; flag:=false; vis[u]:=true; d[u]:=0;
while i<>0 do
begin
v:=vet[i];
if (v<>father)and(cir[v])and(v<>root) then
begin
flag:=true; dfs(v,u);
d[u]:=d[u]+d[v]+dist[i];
end;
i:=next[i];
end;
if u=root then exit; //恶心细节处理
k:=max(1,du[u]);
if flag then begin k:=du[u]+1; d[u]:=(d[u]+p[u])/k; end
else d[u]:=f[u];
end;
begin
read(n,m);
for i:=1 to m do
begin
read(x,y,z);
add(x,y,z); add(y,x,z);
end;
if n=m then
begin
circle(1,0); //找环
fillchar(vis,sizeof(vis),false);
for i:=1 to n do
if cir[i] then begin root:=i; down(i); end;
for i:=1 to n do
if cir[i] then
begin root:=i; dfs(i,0); g[i]:=d[i]; end;
for i:=1 to n do d[i]:=p[i];
for i:=1 to n do
if cir[i] then begin d[i]:=d[i]+g[i]; du[i]:=du[i]+2; end;
for i:=1 to n do
if cir[i] then up(i,0);
for i:=1 to n do
ans:=ans+d[i]/du[i];
writeln(ans/n:0:2);
end else
begin
root:=1;
down(1);
for i:=1 to n do
d[i]:=p[i];
up(1,0);
for i:=1 to n do
ans:=ans+d[i]/du[i];
writeln(ans/n:0:2);
end;
end.

NOI 2012 【迷失游乐园】的更多相关文章

  1. BZOJ 2878: [Noi2012]迷失游乐园( 树形dp )

    一棵树的话直接树形dp(求出往下走和往上走的期望长度). 假如是环套树, 环上的每棵树自己做一遍树形dp, 然后暴力枚举(环上的点<=20)环上每个点跑经过环上的路径就OK了. -------- ...

  2. BZOJ 2878 【NOI2012】 迷失游乐园

    题目链接:迷失游乐园 这道题也没有传说中的那么难写吗→_→ 似乎有篇博客讲得特详细……附上链接:戳这里 如果这道题不是基环树,而就是一棵树的话,我们来考虑改怎么做.因为树上的路径只有向上.向下两种走法 ...

  3. 【BZOJ】【2878】【NOI2012】迷失游乐园

    树形+基环树DP/数学期望 然而我并不会做…… 题解戳这里:http://blog.csdn.net/u011265346/article/details/46328543 好吧先考虑一个简单点的,当 ...

  4. 【BZOJ2878】【NOI2012】迷失游乐园(动态规划)

    [BZOJ2878][NOI2012]迷失游乐园(动态规划) 题面 BZOJ 题解 记得以前考试的时候做过这道题目 这题的暴力还是非常显然的,每次\(dfs\)一下就好了. 时间复杂度\(O(n^2) ...

  5. 【BZOJ 2878】 2878: [Noi2012]迷失游乐园 (环套树、树形概率DP)

    2878: [Noi2012]迷失游乐园 Description 放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩.进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点.m ...

  6. 【NOI2012】迷失游乐园

    题目链接:迷失游乐园(BZOJ)  迷失游乐园(Luogu) 独立完成的题,写一发题解纪念一波~ 模拟完样例大概可以知道是道树形DP了. 观察数据范围,发现是基环树,至少会有一个环. 先从树的部分开始 ...

  7. 「NOI2012」迷失游乐园

    「NOI2012」迷失游乐园 题目描述 放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩. 进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点.m条道路的无向连通图,且该 ...

  8. [bzoj2878][Noi2012]迷失游乐园(基环树dp)

    [bzoj2878][Noi2012]迷失游乐园(基环树dp) bzoj luogu 题意:一颗数或是基环树,随机从某个点开始一直走,不走已经到过的点,求无路可走时的路径长期望. 对于一棵树: 用两个 ...

  9. 2878: [Noi2012]迷失游乐园 - BZOJ

    Description 放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩.进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点.m条道路的无向连通图,且该图中至多有一个环( ...

随机推荐

  1. steam 数据转换

    目录 数组和集合互转 数组转集合 方法一 遍历 方法二 asList 方法三 steam 集合转数组 方法一 循环 方法二 toArray 方法三 steam 小结 string转为Character ...

  2. 解放生产力:Spring Boot的注解校验

    关于对象入参的校验,我们可能第一个想到的就是在Controller层或者Service层增加很多if else的判断,如: if (user.getPassword() == "" ...

  3. Kubernetes K8S在IPVS代理模式下Service服务的ClusterIP类型访问失败处理

    Kubernetes K8S使用IPVS代理模式,当Service的类型为ClusterIP时,如何处理访问service却不能访问后端pod的情况. 背景现象 Kubernetes K8S使用IPV ...

  4. 在CentOs7上yum安装redis

    在开始安装步骤之前,先把我的CentOs和redis版本号列出来: # 系统/软件 版本号 1 CentOS7 CentOS Linux release 7.2.1511 (Core) 2 redis ...

  5. Java实现打开google浏览器

    说明: 博主的Google浏览器版本:75.0.3770.142,如果运行异常,需要自行查找对应版本的驱动(chromedriver.exe) 需要的jar包: https://pan.baidu.c ...

  6. spring5源码 -- IOC容器设计理念和核心注解的作用

    一. spring源码整体脉络介绍及源码编译 二. Spring IOC的源码深入学习 2.1 ioc容器的加载过程(重要) 2.2 bean的生命周期源码深度剖析 2.3 循环依赖总结讲解 2.4 ...

  7. [剑指Offer]66-构建乘积数组

    题目 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]A[1]...A[i-1]A[i+1]...A[n-1].不能使用除法. 题 ...

  8. 抢先学鸿蒙(HarmonyOS)2.0,你就是下一个大咖!

        1. 你不知道的鸿蒙(HarmonyOS)   2020年9月10日,华为开发者大会发布了鸿蒙(HarmonyOS)2.0.我在2020-9-11日也发布了全球首套鸿蒙2.0 App开发视频课 ...

  9. 如何在项目中使用composer的相关功能

    最近要在公司的magento项目中引用第三方库,用了composer来进行管理,composer还是非常方便的: 1.在应用的根目录下添加文件:composer.json {    "nam ...

  10. 工作10年后,再看String s = new String("xyz") 创建了几个对象?

    这个问题相信每个学习java的同学都不陌生,作为一个经典的面试题,到现在工作这么多年了我真是认为挺操蛋的一个问题,在网上到现在你仍然可以看见很多讨论这个问题的人,其中不乏工作很多年的人都有争论,我认为 ...