BZOJ 2878 [Noi2012]迷失游乐园 树形期望DP+基环树

题意:链接

方法:树形期望DP+基环树

解析:

首先先看前50%的数据

是一棵树

那么我们可以搞树形DP

然后设几个正常的数组

sum[i]代表i走i的子节点的期望的和。

down[i]代表从底下走到i的期望。

size[i]代表i的儿子个数

up[i]代表从i往上走的期望

然后就可以推式子了

显而易见地可以推出来up的式子

然后有一些奇怪的关于根节点的特判,注意一下就OK了。

然后后50%

我们发现它是一个基环树?

那么首先可以乱搞出来环上的点,然后记录一下这个环上的点的连接方式,求一下相邻两点的距离什么的。

之后呢?

对于每一个环上的点,扫一遍以它为根,不走环的树形DP,同前50%,能扫出其对应子树的那些值。

之后就是更新up了

对于环上的点,我们可以是逆时针走可以是顺时针走,所以先求逆时针,后求顺时针,除个2就行。

但是怎么求呢?

来枚举这个过程。

先从环上一个点,朝逆时针方向走。

找到一个点后,用那个点的down值以及二者的距离更新我们选取的点的up值。

然后别忘了算算一下概率

不妨假设顺序为1 2 3

那我们枚举到1时

走第二个节点的概率是 1size[1]+1

从第二个走向第三个的概率也有,所以需要乘一下。

之后就弄完了~不过还是有很多特判

尤其是我的对于前后50%的第二个搜索不一样。。。

也懒得归一了=-=

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100010
using namespace std;
int n,m,cnt,tot;
struct node
{
    int from,to,val,next;
}edge[N<<1];
int head[N];
int size[N];
int fa[N];
int cntfa[N];
int v[N];
int inc[N];
int cir[N];
int pre[N];
int nex[N];
double sum[N];
double up[N];
double dis[N];
double down[N];
void init()
{
    memset(head,-1,sizeof(head));
    cnt=1;
}
void edgeadd(int from,int to,int val)
{
    edge[cnt].from=from,edge[cnt].to=to,edge[cnt].val=val;
    edge[cnt].next=head[from];
    head[from]=cnt++;
}
int flag;
void getcir(int now,int ff)
{
    fa[now]=ff;
    v[now]=1;
    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        if(flag)return;
        int to=edge[i].to;
        if(to==ff)continue;
        if(v[to]!=0)
        {
            cir[++tot]=to;
            inc[to]=tot;
            cntfa[to]=2;
            int tmp=now;
            do
            {
                cir[++tot]=tmp;
                cntfa[tmp]=2;
                inc[tmp]=tot;
                tmp=fa[tmp];
            }while(tmp!=to);
            flag=1;return ;
        }else
        {
            getcir(to,now);
        }
    }
}
double map[25][25];
int ccccnt;
void dfscir(int now,int fa)
{
    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!inc[to]||to==fa||ccccnt==tot)continue;
        ccccnt++;
        pre[to]=now;
        nex[now]=to;
        dfscir(to,now);
        map[inc[to]][inc[now]]=map[inc[now]][inc[to]]=(double)edge[i].val;
        break;
    }
}
void dfs(int now,int fa)
{
    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to==fa||inc[to])continue;
        cntfa[to]=1;
        dfs(to,now);
        dis[to]=edge[i].val;
        size[now]++;
        sum[now]+=down[to]+edge[i].val;
    }
    if(size[now]!=0)down[now]=sum[now]/(double)size[now];
}
void dfs3(int now,int fa,int root)
{
    if(now!=1)
    {
        double tmp=sum[fa]-dis[now]+up[fa];
        if(size[now]!=0)tmp-=sum[now]/size[now];
        if(fa==1)up[now]=tmp/(size[fa]-1);
        else up[now]=tmp/size[fa];
        up[now]+=dis[now];
    }
    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to==fa)continue;
        dfs3(to,now,root);
    }
}
void dfs2(int now,int fa,int root)
{
    if(now!=root)
    {
        double tmp=sum[fa]-dis[now]+up[fa];
        if(inc[fa])tmp+=up[fa];
        if(size[now]!=0)tmp-=sum[now]/size[now];
        if(inc[fa])up[now]=tmp/(size[fa]+1);
        else up[now]=tmp/size[fa];
        up[now]+=dis[now];
    } 
    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to==fa)continue;
        dfs2(to,now,root);
    }
}
double ans;
int main()
{
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        edgeadd(x,y,z);
        edgeadd(y,x,z);
    }
    if(m==n-1)
    {
        dfs(1,-1);
        dfs3(1,-1,1);
        for(int i=1;i<=n;i++)
        {
            if(i==1)ans+=(sum[i]+up[i])/(double)(size[i]);
            else ans+=(sum[i]+up[i])/(double)(size[i]+1);
        }
        ans/=(double)n;
        printf("%.5lf\n",ans);
    }else
    {
        getcir(1,-1);
        dfscir(cir[1],-1);
        for(int i=1;i<=n;i++)if(inc[i])dfs(i,-1);
        for(int i=1;i<=tot;i++)
        {
            double k=1;
            int now=cir[i];
            for(int j=nex[now];j!=now;j=nex[j])
            {
                if(nex[j]!=now)
                {
                    up[now]+=k*(map[inc[pre[j]]][inc[j]]+down[j]*size[j]/(size[j]+1));
                }else
                {
                    up[now]+=k*(map[inc[pre[j]]][inc[j]]+down[j]);
                }
                k/=(size[j]+1);
            }
            k=1;
            for(int j=pre[now];j!=now;j=pre[j])
            {
                if(pre[j]!=now)
                {
                    up[now]+=k*(map[inc[nex[j]]][inc[j]]+down[j]*size[j]/(size[j]+1));
                }else
                {
                    up[now]+=k*(map[inc[nex[j]]][inc[j]]+down[j]);
                }
                k/=(size[j]+1);
            }
            up[now]/=2.0;
        }
        for(int i=1;i<=tot;i++)
        {
            int now=cir[i];
            for(int j=head[now];j!=-1;j=edge[j].next)
            {
                int to=edge[j].to;
                if(!inc[to])dfs2(to,now,now); 
            } 
        }
        double ans=0;
        for(int i=1;i<=n;i++)
        {
            if(inc[i])
            {
                ans+=(up[i]*2+down[i]*size[i])/(double)(2+size[i]);
            }else ans+=(up[i]+down[i]*size[i])/(double)(1+size[i]);
        }
        printf("%.5lf\n",ans/(double)n); 
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值