NOI2012 迷失游乐园 期望+树形dp+基环外向树

来源:互联网 发布:黑马淘淘商城源码 编辑:程序博客网 时间:2024/05/17 22:30

【问题描述】

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

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

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

【输入格式】

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

【输出格式】

  共一行,包含一个实数(保留5位小数),即路径的期望长度。

【输入样例】

4 3
1 2 3
2 3 1
3 4 4

【输出样例】

6.00000

【样例解释】

  样例数据中共有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

【数据范围】

对于100%的数据,1 <= Wi <= 100。
测试点编号     n     m      备注
  1      n=10    m=n-1   保证图是链状
  2      n=100   m=n-1   只有节点1的度数大于2
  3      n=1000   m=n-1     /
  4      n=100000  m=n-1     /
  5      n=100000  m=n-1     /
  6      n=10    m=n       /
  7      n=100   m=n     环中节点个数<=5
  8      n=1000   m=n     环中节点个数<=10
  9      n=100000  m=n     环中节点个数<=15
  10     n=100000  m=n     环中节点个数<=20

——————————————————————————————————————————————————

主要是个细节题。

首先题干和原题有一些差异,主要是我家的题库不支持spj。。。。
很容易发现题目描述的是这个地图可能是树或者基环外向树。那么根据经验,先看树上的怎么办,然后再来看基环外向树上怎么办。一般把树上的弄好了就八九不离十了(也不排除这个一二就是搞不定的情况)。

对于一棵树,考虑最暴力的做法(即以每个结点为根结点去dfs一次),设f(i)表示从i出发走i的子树的期望长度,我们很容易写出这样的方程:
f(i) = sum{ f(j)+w(i,j) }/chd[i]
显然这种算法是O(N^2)的,需要改进。
观察到若某个节点i的父亲是u,那么假设已经算好了u为根结点的情况,现在来尝试计算i为根结点的情况,会发现实际上u除了不可以走到i来,走其他儿子其他情况完全没有发生任何的变化。对于i也就是多了走到u这一种选择,其他情况都没有变化。这个时候i变成了u的父亲,很明显是换根操作。
所以说设ans[i]表示以i为根结点的情况下从i出发的期望距离,就可以写出如下的转移方程:
ans[i] = (f[i] * chd[i] + (ans[u] * (chd[u] + 1) - f[i] - fl) / chd[u] + fl) / (chd[i] + 1)
在进行换根操作的时候chd数组是由以1为根的情况得到的。上面的方程针对的是u!=1的情况,至于u==1的情况就是chd[u]那里会发生一点点变化,同时需要特殊讨论u==1&&chd[u]==1的情况(脑补一下还是很好理解的),方程会有变化。

这样就把树的情况干完了,时间复杂度O(N)。

来考虑一下基环外向树。
很显然题目表示环上的点不超过20个那么怎么乱来都是没毛病的(O(N^2)就算了吧)。思路和上面一样,由于一旦从环上走到树里面去了环上的东西就没用了,那么就先dfs一次找到基环外向树上的环(这一步要注意一下),然后以每个环上的点为根结点去算一下f,过程中不走到环上来。
然后就以环上的每个点i为起点来算ans[i]了,发现可以在环上走两个方向的只有i,其他的点要在环上继续走的话后继是唯一确定了的,当然也有可能中途走到某个点的时候突然就跑到树上去了。
我觉得如果上面树的推导都看懂了的话这个也没有什么好说的了吧。。。注意一个事情,举个例子,以i为起点在环上向右走走到i左边这个点left的时候方程会有变化,left并没有继续在环上走下去这个选项

实际上实在想不出来的还可以看我的代码。。。。。。

AC代码:

#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<algorithm>#include<cmath>#include<queue>#include<set>#include<map>#include<vector>#include<cctype>using namespace std;const int maxn=100005;const double eps=0.0000001;int N,M;struct edge{ int to,next,w,id; }E[maxn<<1];int first[maxn],np,chd[maxn];double f[maxn],g[maxn],ans[maxn];bool onc[maxn],vis[maxn];int fa[maxn],s,t;void _scanf(int &x){    x=0;    char ch=getchar();    while(ch<'0'||ch>'9') ch=getchar();    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();}void add_edge(int u,int v,int w,int id){    E[++np]=(edge){v,first[u],w,id};    first[u]=np;}void data_in(){    _scanf(N);_scanf(M);    int x,y,z;    for(int i=1;i<=M;i++)    {        _scanf(x);_scanf(y);_scanf(z);        add_edge(x,y,z,i);        add_edge(y,x,z,i);    }}void DFS1(int i,int u){    for(int p=first[i];p;p=E[p].next)    {        int j=E[p].to;        if(j==u||onc[j]) continue;        chd[i]++;        DFS1(j,i);        f[i]+=f[j]+E[p].w;    }    if(chd[i]) f[i]*=1.0/chd[i];}void DFS1(int i,int u,int fl){    if(u!=0)    {        if(u!=1) ans[i]=(f[i]*chd[i]+(ans[u]*(chd[u]+1)-f[i]-fl)/chd[u]+fl)/(chd[i]+1);        else if(chd[u]>1) ans[i]=(f[i]*chd[i]+(ans[u]*chd[u]-f[i]-fl)/(chd[u]-1)+fl)/(chd[i]+1);        else ans[i]=(f[i]*chd[i]+fl)/(chd[i]+1);    }    for(int p=first[i];p;p=E[p].next)    {        int j=E[p].to;        if(j==u) continue;        DFS1(j,i,E[p].w);    }}void work1(){    DFS1(1,0);    ans[1]=f[1];    DFS1(1,0,0);    double sum=0;    for(int i=1;i<=N;i++) sum+=ans[i];    sum/=N;    printf("%.5lf\n",sum);}void DFS2(int i,int u,int fid){    vis[i]=1;    fa[i]=u;    for(int p=first[i];p;p=E[p].next)    {        if(E[p].id==fid) continue;        int j=E[p].to;        if(vis[j]) { s=i,t=j; continue; }        DFS2(j,i,E[p].id);    }}void dfs(int i){    vis[i]=1;    for(int p=first[i];p;p=E[p].next)    {        int j=E[p].to;        if(vis[j]||!onc[j]) continue;        dfs(j);        g[i]=g[j]+E[p].w;    }    if(abs(g[i])<=eps) g[i]=f[i];    else g[i]=(g[i]+f[i]*chd[i])/(chd[i]+1);} void calc(int i){    for(int p=first[i];p;p=E[p].next)    {        int j=E[p].to;        if(!onc[j]) continue;        memset(vis,0,sizeof(vis));        memset(g,0,sizeof(g));        vis[i]=1;        dfs(j);        ans[i]+=g[j]+E[p].w;    }    ans[i]=(ans[i]+f[i]*chd[i])/(chd[i]+2);}void DFS2(int i,int u,int fl,int rt){    if(u!=0)    {        if(u!=rt) ans[i]=(f[i]*chd[i]+(ans[u]*(chd[u]+1)-f[i]-fl)/chd[u]+fl)/(chd[i]+1);        else ans[i]=(f[i]*chd[i]+(ans[u]*(chd[u]+2)-f[i]-fl)/(chd[u]+1)+fl)/(chd[i]+1);    }    for(int p=first[i];p;p=E[p].next)    {        int j=E[p].to;        if(j==u||onc[j]) continue;        DFS2(j,i,E[p].w,rt);    }}void work2(){    DFS2(1,0,0);    fa[s]=t;    int now=s;    do{ onc[now]=1,now=fa[now]; }while(now!=s);    now=s;    do{ DFS1(now,0); now=fa[now]; }while(now!=s);    now=s;    do{ calc(now); now=fa[now]; }while(now!=s);    now=s;    do{ DFS2(now,0,0,now); now=fa[now]; }while(now!=s);    double sum=0;    for(int i=1;i<=N;i++) sum+=ans[i];    sum/=N;    printf("%.5lf\n",sum);}int main(){    freopen("test.in","r",stdin);    freopen("test.out","w",stdout);    data_in();    if(N-1==M) work1();    else work2();    return 0;}
阅读全文
0 0
原创粉丝点击