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

时间:2023-03-09 07:54:04
【BZOJ】【2878】【NOI2012】迷失游乐园

树形+基环树DP/数学期望


  然而我并不会做……

  题解戳这里:http://blog.****.net/u011265346/article/details/46328543

  好吧先考虑一个简单点的,当m=n-1时,整个是一个树形的结构,无根树我们一般还是转成有根树来处理……然后既然是无法回头的,那么我们可以定一下方向:向下或者向上(废话)

  定义一下:

  son[x]为x的儿子的数量

  down[x]表示从x这个点出发,向叶子们走的期望长度。

  怎么算呢?其实就是所有可能的情况(所有的儿子)加起来,再求个平均,所以我们有$$down[x]=\frac{\sum (down[y]+len(x->y))}{son[x]}$$

  up[x]表示从x这个点出发,经过父亲,走到某个叶子的期望长度。

  这个看上去好难算啊……怎么算呢?我们先算出来每个结点的down[x],然后从上往下DP,这里我们需要考虑的是x的父亲 f ,我们从x向上走到他父亲 f 后(len(x->f)),可以向 f 其他的儿子走,这种方案的期望长度之和为$$ son[f]*down[f]-down[x]-len(f->x) $$如果是继续向上,这种方案的期望长度为$up[f]$,所以有$$up[x]=len(x->f) + \frac{ son[f]*down[f]-down[x]-len(f->x) + up[f]}{son[f]-1+1}$$这里son[f]-1表示是向其他儿子走,+1表示的是继续向上,这么多种方案的概率是相等的。

  那么答案怎么算?当然是$$ans=\sum_{i=1}^n \frac{down[i]*son[i]+up[i]}{son[i]+1} $$

  好的到这里我们就解决了树上的问题,那么环套树其实就是需要特殊处理一下 环上的结点以及与环直接相连的结点,怎么做呢?

  肯定是要先找环的啦= =那么我们dfs搞搞找出环上所有结点,题目限制这样的结点不多,就20个= =

  有一个东西是跟树的情况一样的,那就是从环上某个结点直接向与它相连的外向树上走的期望长度,也就是所有的down[cir[i]],这里cir[i]表示环上的点。

  算出down[cir[i]]以后,跟据刚刚的经验,我们就可以用down来算up啦!同样是从上往下算up的值,只不过这里的“树根”变成了一个环,环上的点的up值其实就是沿着环走到其他的任意一个环上的点,然后再向下走的期望长度啦,举个例子吧:(其实这一段看代码比较好……)

  环上的点编号为1、2、3、4、5,那么对于1来说,顺时针走的话,走到2的概率为1,走到3的概率为$\frac{1}{son[2]+1}$,走到4的话就再乘$\frac{1}{son[3]+1}$……逆时针走的话同理。

  同时我们沿着环每走到一个位置就加上从这里向外向树走的期望长度(注意绕一圈走到头的地方与之前的不一样,因为出发点不可能经过两次)。

  表达式比较难写……好吧其实是我懒,而且长得并不好看,还是看代码比较好:

 F(i,,tot){
int x=cir[i];
double k=;
for(int j=nex[x];j!=x;j=nex[j]){
if (nex[j]!=x)
up[x]+=k*(len[hash[pre[j]]][hash[j]]+down[j]*son[j]/(son[j]+));
else
up[x]+=k*(len[hash[pre[j]]][hash[j]]+down[j]);
k/=(son[j]+);
}
k=;
for(int j=pre[x];j!=x;j=pre[j]){
if (pre[j]!=x)
up[x]+=k*(len[hash[nex[j]]][hash[j]]+down[j]*son[j]/(son[j]+));
else
up[x]+=k*(len[hash[nex[j]]][hash[j]]+down[j]);
k/=(son[j]+);
}
up[x]/=;
}

  算出所有环上的点的up[i]以后,外向树的up[i]就跟树上情况一样了……不过由于环上的点的父节点相当于是有两个,所以这里重新定义一个fa[i],表示 i 的父亲结点的数量。那么有$$up[x]=len(x->f) + \frac{ son[f]*down[f]-down[x]-len(f->x) + up[f]*fa[f] }{son[f]-1+fa[f]}$$

另外,对于上面的所有公式,要小心分母可能为0的情况。

 /**************************************************************
Problem: 2878
User: Tunix
Language: C++
Result: Accepted
Time:556 ms
Memory:9876 kb
****************************************************************/ //BZOJ 2878
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define rep(i,n) for(int i=0;i<n;++i)
#define F(i,j,n) for(int i=j;i<=n;++i)
#define D(i,j,n) for(int i=j;i>=n;--i)
#define pb push_back
using namespace std;
inline int getint(){
int v=,sign=; char ch=getchar();
while(ch<''||ch>''){ if (ch=='-') sign=-; ch=getchar();}
while(ch>=''&&ch<=''){ v=v*+ch-''; ch=getchar();}
return v*sign;
}
const int N=1e5+,INF=~0u>>;
typedef long long LL;
/******************tamplate*********************/
int to[N<<],nxt[N<<],head[N],cnt;
double l[N<<];
void ins(int x,int y,double z){
to[++cnt]=y; nxt[cnt]=head[x]; head[x]=cnt; l[cnt]=z;
} int n,m; int vis[N],flag;
double son[N],fa[N],up[N],down[N];
int cir[N],tot,hash[N];
int pre[N],nex[N];
double len[][]; void Findcir(int x,int f){
vis[x]=;
for(int i=head[x];i;i=nxt[i])
if (to[i]!=f){
if (vis[to[i]]){
flag=to[i];
return;
}
Findcir(to[i],x);
if (flag>){
if (flag==x) flag=-;
return;
}
if (flag==-) break;
}
vis[x]=;
}
void dfs_cir(int x,int f){
if (hash[x]) return;
cir[++tot]=x;
hash[x]=tot;
fa[x]=;
for(int i=head[x];i;i=nxt[i]){
if (to[i]==f) continue;
if (!vis[to[i]]) continue; pre[to[i]]=x;
nex[x]=to[i];
dfs_cir(to[i],x);
len[hash[x]][hash[to[i]]]=len[hash[to[i]]][hash[x]]=l[i];
break;
}
} void dfsdown(int x,int f){
for(int i=head[x];i;i=nxt[i])
if (!vis[to[i]] && to[i]!=f){
fa[to[i]]=;
dfsdown(to[i],x);
son[x]++;
down[x]+=down[to[i]]+l[i];
}
if (son[x]) down[x]/=son[x];
} void dfsup(int x,int f,double ee){
up[x]=ee;
if (fa[f]+son[f]>)
up[x]+=(fa[f]*up[f]+son[f]*down[f]-down[x]-ee)/(fa[f]+son[f]-);
for(int i=head[x];i;i=nxt[i])
if (to[i]!=f) dfsup(to[i],x,l[i]);
} int main(){
#ifndef ONLINE_JUDGE
freopen("2878.in","r",stdin);
freopen("2878.out","w",stdout);
#endif
n=getint(); m=getint();
F(i,,m){
int x=getint(),y=getint(),z=getint();
ins(x,y,z); ins(y,x,z);
}
Findcir(,);
if (m<n){
dfsdown(,);
for(int i=head[];i;i=nxt[i])
dfsup(to[i],,l[i]);
}else{
F(i,,n)
if (vis[i]){
dfs_cir(i,);
break;
}
F(i,,tot) dfsdown(cir[i],);
F(i,,tot){
int x=cir[i];
double k=;
for(int j=nex[x];j!=x;j=nex[j]){
if (nex[j]!=x)
up[x]+=k*(len[hash[pre[j]]][hash[j]]+down[j]*son[j]/(son[j]+));
else
up[x]+=k*(len[hash[pre[j]]][hash[j]]+down[j]);
k/=(son[j]+);
}
k=;
for(int j=pre[x];j!=x;j=pre[j]){
if (pre[j]!=x)
up[x]+=k*(len[hash[nex[j]]][hash[j]]+down[j]*son[j]/(son[j]+));
else
up[x]+=k*(len[hash[nex[j]]][hash[j]]+down[j]);
k/=(son[j]+); }
up[x]/=;
}
F(j,,tot){
for(int i=head[cir[j]];i;i=nxt[i])
if (!hash[to[i]]) dfsup(to[i],cir[j],l[i]);
}
}
double ans=;
F(i,,n) ans+=(up[i]*fa[i]+down[i]*son[i])/(fa[i]+son[i]);
printf("%.5lf\n",ans/n);
return ;
}

2878: [Noi2012]迷失游乐园

Time Limit: 10 Sec  Memory Limit: 512 MBSec  Special Judge
Submit: 503  Solved: 321
[Submit][Status][Discuss]

Description


假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩。进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点、m条道路的无向
连通图,且该图中至多有一个环(即m只可能等于n或者n-1)。小Z现在所在的大门也正好是一个景点。小Z不知道什么好玩,于是他决定,从当前位置出发,
每次随机去一个和当前景点有道路相连的景点,并且同一个景点不去两次(包括起始景点)。贪玩的小Z会一直游玩,直到当前景点的相邻景点都已经访问过为止。
小Z所有经过的景点按顺序构成一条非重复路径,他想知道这条路径的期望长度是多少?小Z把游乐园的抽象地图画下来带回了家,可是忘了标哪个点是大门,他只
好假设每个景点都可能是大门(即每个景点作为起始点的概率是一样的)。同时,他每次在选择下一个景点时会等概率地随机选择一个还没去过的相邻景点。

Input

第一行是两个整数n和m,分别表示景点数和道路数。 接下来行,每行三个整数Xi, Yi, Wi,分别表示第i条路径的两个景点为Xi, Yi,路径长Wi。所有景点的编号从1至n,两个景点之间至多只有一条道路。

Output

共一行,包含一个实数,即路径的期望长度。

Sample Input

4 3
1 2 3
2 3 1
3 4 4

Sample Output

6.00000000

【样例解释】样例数据*有6条不同的路径: 路径 长度 概率
1-->4 8 1/4
2-->1 3 1/8
2-->4 5 1/8
3-->1 4 1/8
3-->4 4 1/8
4-->1 8 1/4
因此期望长度 = 8/4 + 3/8 + 5/8 + 4/8 + 4/8 + 8/4 = 6.00
【评分方法】本题没有部分分,你程序的输出只有和标准答案的差距不超过0.01时,才能获得该测试点的满分,否则不得分。
【数据规模和约定】对于100%的数据,1 <= Wi <= 100。 测试点编号 n m 备注
1 n=10 m = n-1 保证图是链状
2 n=100 只有节点1的度数大于2
3 n=1000 /
4 n=100000 /
5 n=100000 /
6 n=10 m = n /
7 n=100 环中节点个数<=5
8 n=1000 环中节点个数<=10
9 n=100000 环中节点个数<=15
10 n=100000 环中节点个数<=20

HINT

Source

[Submit][Status][Discuss]