这道题,额,反正我是刚了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. java 区块

    方法区:存放staic变量,方法签名,类信息,字段等 堆:存放对象数据,string常量 栈:存放对象的引用,操作数,没逃逸但是逃逸分析且被编译器产生逃逸优化的对象数据

  2. 听过N次还是不会之:浏览器输入url后到底经历了什么

    有没有这种场景:当你被问起某一项知识点时,你大脑里想起经常看到过这样的问题,可是具体是怎么样就是说不清楚. 好吧,我就是这样的,于是整理一下,实在记不住,以后找起来也方便. 当你在浏览器地址栏里输入一 ...

  3. Web最最基础2

    网页元素HTML 列表 (1)无序列表 <ul> <li>****</li> <li>***</li></ul> 更多样式:&l ...

  4. java初探(1)之秒杀的业务简单实现

    前言 秒杀的业务场景广泛存在于电商当中,即有一个倒计时的时间限制,当倒计时为0时,秒杀开始,秒杀之后持续很小的一段时间,而且秒杀的商品很少,因此会有大量的顾客进行购买,会产生很大的并发量,从而创造技术 ...

  5. Vue官方文档Vue.extend、Vue.component、createElement、$attrs/$listeners、插槽的深入理解

    一.Vue.extend({}). 看官网文档介绍,Vue.extend({})返回一个Vue的子类,那么这个Vue子类是啥玩意儿呢?我直观感觉它就是创建出一个组件而已啊,那么它又和Vue.compo ...

  6. Git——从安装到连接GitHub

    安装Git Windows平台上很轻松的,直接点击地址Git下载进行下载,之后基本就是下一步,安装成功. 在所需要操作的项目下右键,点击"Git Bash Here"弹出Git命令 ...

  7. Mybati源码解析篇之六剑客!!!

    目录 前言 环境版本 Mybatis的六剑客 SqlSession 有何方法 语句执行方法 立即批量更新方法 事务控制方法 本地缓存方法 获取映射方法 有何实现类? Executor 实现类 Base ...

  8. appium多线程自动化

    基于上篇讲述的appium自动启动停止.测试服务.对controller文件进行相应的修改 1.首先对start_server函数,应采用多线程模式启动多个server,如下 其中启动的每个线程函数s ...

  9. jmeter做简单的压测

    一.JMeter概述jmeter除了可以做借口测试外,还可以做压力测试:首先介绍jmeter中各个组件在压力测试中扮演的角色 1)线程(Threads(Users))即虚拟用户,线程组里可设置需要模拟 ...

  10. 【微信小程序】常用组件及自定义组件

    (一) 常用标签 组件你可以理解为传统页面开发时候的各种标签,例如 div span 等等,我这里只说一些常用的,这样就能能搭建出一个基本的页面了,但是如果想要更加美观以及拥有更好的体验,就需要 XS ...