2878: [Noi2012]迷失游乐园 基环树+DP+概率与期望

本文介绍了一种计算基环树上从任意节点出发到达另一节点期望步数的方法。通过树形DP技术和概率分析,文章详细阐述了如何求解基环树中各节点的上下行走期望,并给出了完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不妨用downi表示从点i往下走的期望步数,upi表示从点i往上走的期望步数。
soni表示点i的儿子个数,fai表示点i的父亲个数(基环树上的点有两个父亲)
那么答案显然等于

ans=ni=1downi×soni+upi×faisoni+fain

如何考虑求downisoni
直接来说基环树上的情况,树上的情况可以看做基环树中环的某个点为根的子树的情况。
首先dfs找环,如何从环上每个点往下做树形DP,用w(u,v)表示边(u,v)的长度。
首先自底向上DP,求出downx,不妨令y表示x的儿子。
那么有

downx=ydowny+w(x,y)sonx

这样可以求出所有的downx
如何考虑不在环上的点,假设我们已经求出了环上点的up,那么自顶向下DP。
x表示树中的点,用f表示父亲。
upx=w(x,f)+downf×sonfdownxw(x,f)+upfsonf

那么考虑求环上点的up,环上的点一定是沿环的某个方向走然后进入了某一棵子树然后走到子树的底部。
我们不妨对点进行编号,假设从1号点出发,沿顺时针走,那么走到二号点的概率为1,走进二号店子树的概率为son2son2+1然后以此类推,逆时针做一遍就可以了。
最后不要忘记将顺时针和逆时针的答案加起来/2。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int N=100005;
int n,m,cnt,cir[25],head[N],next[N<<1],list[N<<1],from[N];
double key[N<<1],down[N],up[N],son[N],fa[N],pre[N];
bool vis[N];

inline int read()
{
    int a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}

inline void insert(int x,int y,double z)
{
    next[++cnt]=head[x];
    head[x]=cnt;
    list[cnt]=y;
    key[cnt]=z;
}

void dfs1(int x,int f)
{
    for (int i=head[x];i;i=next[i])
        if (list[i]!=f&&!vis[list[i]]) 
            dfs1(list[i],x),son[x]++;
    if (!son[x]) return;
    for (int i=head[x];i;i=next[i])
        if (list[i]!=f&&!vis[list[i]])
            down[x]+=down[list[i]]+key[i];
    down[x]/=son[x];
}

void dfs2(int x,int f)
{
    if (!son[x]) return;
    for (int i=head[x];i;i=next[i])
        if (list[i]!=f&&!vis[list[i]]) up[list[i]]+=key[i];
    if (son[x]+fa[x]>1)
        for (int i=head[x];i;i=next[i])
            if (list[i]!=f&&!vis[list[i]])
                up[list[i]]+=(down[x]*son[x]-down[list[i]]-key[i]+up[x]*fa[x])/(son[x]-1+fa[x]);
    for (int i=head[x];i;i=next[i])
        if (list[i]!=f&&!vis[list[i]]) dfs2(list[i],x);
}

void dfs_circle(int x)
{
    vis[x]=true;
    for (int i=head[x];i;i=next[i])
        if (!vis[list[i]]) from[list[i]]=x,pre[list[i]]=key[i],dfs_circle(list[i]);
        else if (list[i]!=from[x])
        {
            from[list[i]]=x; pre[list[i]]=key[i];
            for (int j=x;j!=list[i];j=from[j]) cir[++cir[0]]=j;
            cir[++cir[0]]=list[i];
            return;
        }
}

int main()
{
    n=read(); m=read();
    for (int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        double w; 
        scanf("%lf",&w);
        insert(u,v,w); insert(v,u,w);
    }
    if (m==n-1)
    {
        for (int i=1;i<=n;i++) fa[i]=1;
        memset(vis,0,sizeof(vis));
        fa[1]=0;
        dfs1(1,0);
        dfs2(1,0);
    }
    else
    {
        dfs_circle(1);
        for (int i=1;i<=n;i++) fa[i]=1;
        for (int i=1;i<=cir[0];i++) fa[cir[i]]=2;
        memset(vis,0,sizeof(vis));
        for (int i=1;i<=cir[0];i++) vis[cir[i]]=1;
        for (int i=1;i<=cir[0];i++) dfs1(cir[i],0);
//      for (int i=1;i<=cir[0];i++)
//          cout << from[cir[i]] << " "; cout << endl;
//      for (int i=1;i<=cir[0];i++)
//          cout << pre[cir[i]] << " ";
//      cout <<"!!!!!!!";   
        for (int i=1;i<=cir[0];i++)
        {
            double P;
            P=1.0;
            for (int j=1;j<cir[0];j++)
            {
                int now=cir[(i+j-1)%cir[0]+1];
//              cout << cir[i] << " " << now << " " << pre[cir[(i+j-2)%cir[0]+1]] << endl;
                if (j!=cir[0]-1) 
                    up[cir[i]]+=P*(pre[cir[(i+j-2)%cir[0]+1]]+down[now]*son[now]/(son[now]+1));
                else
                    up[cir[i]]+=P*(pre[cir[(i+j-2)%cir[0]+1]]+down[now]);
                P/=son[now]+1;
            }
//          cout << endl;
            P=1.0;
            for (int j=1;j<cir[0];j++)
            {
                int now=cir[(i-j+cir[0]-1)%cir[0]+1];
//              cout << cir[i] << " " << now << " " << pre[now] << endl;
                if (j!=cir[0]-1)
                    up[cir[i]]+=P*(pre[now]+down[now]*son[now]/(son[now]+1));
                else
                    up[cir[i]]+=P*(pre[now]+down[now]);
                P/=son[now]+1;
            }
//          cout << endl;
            up[cir[i]]/=2;
        }
        for (int i=1;i<=cir[0];i++) dfs2(cir[i],0);
    }
//  for (int i=1;i<=cir[0];i++)
//      cout << son[cir[i]] << " " ;
    double ans=0.0;
    for (int i=1;i<=n;i++)
        ans+=(down[i]*son[i]+up[i]*fa[i])/(son[i]+fa[i]);
    printf("%.5lf\n",ans/n);
    return 0;
}           
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值