BZOJ2878: [Noi2012]迷失游乐园

BZOJ2878: [Noi2012]迷失游乐园

树形Dp


题解:

orz orz orz orz
ORZ Tunix ORZ
orz orz orz orz


简单写一下,怕自己忘了。。。

先考虑没有环的情况。

令点1为树根,设 down[i] 表示从i开始向下走的期望步数, up[i] 表示往上走的。

down[i] 的计算很简单:

down[i]=(down[j]+w[i][j])son[i]

考虑 up[i] ,点i走到父亲f后,可以顺着其他的儿子向下走,也可以继续往上。所以:

up[i]=len[i][f]+down[f]son[f]down[i]len[f][i]+up[f]son[f]1+1

最后,以每个点为出发点统计答案:

ans=ni=1down[i]son[i]+up[i]son[i]+1n


考虑环呢?注意最多只有一个环。

不在环上的点一样算,环上的down也一样算。

环上的点的up值其实就是沿着环走到其他的任意一个环上的点,然后再沿外向树向下走的期望长度。

这一段直接看代码吧。

for(int i=1;i<=tot;i++){
    int now=cir[i];
    double k=1;
    for(int x=nxt[now];x!=now;x=nxt[x]){
        if(nxt[x]!=now)
            up[now]+=k*(len[id[pre[x]]][id[x]]+down[x]*son[x]/(son[x]+1));
        else
            up[now]+=k*(len[id[pre[x]]][id[x]]+down[x]);
        k/=(son[x]+1);
    }
    k=1;
    for(int x=pre[now];x!=now;x=pre[x]){
        if(pre[x]!=now)
            up[now]+=k*(len[id[nxt[x]]][id[x]]+down[x]*son[x]/(son[x]+1));
        else
            up[now]+=k*(len[id[nxt[x]]][id[x]]+down[x]);
        k/=(son[x]+1);
    }
    up[now]/=2;
}

两个并列的循环分别是顺时针、逆时针,最后/2。k是走到这个点的概率,每次乘以 1son[x]+1 .

另外环上的点算是有两个父亲,于是定义 pa[i] 表示i的父亲个数,则 up[i] 的式子化为:

up[i]=len[i][f]+down[f]son[f]down[i]len[f][i]+up[f]fa[i]son[f]1+pa[i]


注意除0!

Code:

#include <iostream>
#include <cstring>
#include <cstdio>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
const int N = 100005;

int n,m,flag,tot; double up[N],down[N]; bool bo[N];
int len[21][21],cir[21],id[N],pre[N],nxt[N],fa[N],son[N];

struct Edge{
    int to,next,w;
} e[N*2];
int head[N], ec=0;
void add(int a,int b,int w){
    ec++; e[ec].to=b; e[ec].w=w;
    e[ec].next=head[a]; head[a]=ec;
}

void findcir(int u,int f){
    bo[u]=true;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to; 
        if(v==f) continue;
        if(bo[v]){ flag=v; return; }
        findcir(v,u);
        if(flag>0){
            if(flag==u) flag=-1;
            return;
        }
        if(flag==-1) break;
    }
    bo[u]=false;
}

void dfscir(int u,int f){
    if(id[u]) return;
    id[u]=++tot; cir[tot]=u; fa[u]=2;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==f) continue;
        if(!bo[v]) continue;
        nxt[u]=v; pre[v]=u;
        dfscir(v,u);
        len[id[u]][id[v]]=len[id[v]][id[u]]=e[i].w;
        break;
    }
} 

void dfsdown(int u,int f){
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==f || bo[v]) continue;
        fa[v]=1; dfsdown(v,u);
        son[u]++; down[u]+=down[v]+e[i].w;
    }
    if(son[u]) down[u]/=son[u];
}

void dfsup(int u,int f,int w){
    up[u]=w;
    if(son[f]+fa[f]>1){
        up[u]+=(up[f]*fa[f]+son[f]*down[f]-down[u]-w)/(son[f]+fa[f]-1);
    }
    for(int i=head[u];i;i=e[i].next){
        if(e[i].to!=f) dfsup(e[i].to,u,e[i].w);
    }
}

int main(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    cin>>n>>m; int a,b,w;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&w);
        add(a,b,w); add(b,a,w);
    }
    if(m<n){
        dfsdown(1,0);
        for(int i=head[1];i;i=e[i].next)
            dfsup(e[i].to,1,e[i].w);
    }
    else{
        findcir(1,0);

//      for(int i=1;i<=n;i++) if(bo[i]) D(i); E;

        for(int i=1;i<=n;i++){
            if(bo[i]){ dfscir(i,0); break; }
        }

//      for(int i=1;i<=n;i++){ if(bo[i]){ D(i); D(pre[i]); D(nxt[i]); E; } }

        for(int i=1;i<=tot;i++){ dfsdown(cir[i],0); }
        for(int i=1;i<=tot;i++){
            int now=cir[i];
            double k=1;
            for(int x=nxt[now];x!=now;x=nxt[x]){
                if(nxt[x]!=now)
                    up[now]+=k*(len[id[pre[x]]][id[x]]+down[x]*son[x]/(son[x]+1));
                else
                    up[now]+=k*(len[id[pre[x]]][id[x]]+down[x]);
                k/=(son[x]+1);
            }
            k=1;
            for(int x=pre[now];x!=now;x=pre[x]){
                if(pre[x]!=now)
                    up[now]+=k*(len[id[nxt[x]]][id[x]]+down[x]*son[x]/(son[x]+1));
                else
                    up[now]+=k*(len[id[nxt[x]]][id[x]]+down[x]);
                k/=(son[x]+1);
            }
            up[now]/=2;
        }
        for(int j=1;j<=tot;j++){
            for(int i=head[cir[j]];i;i=e[i].next)
                if(!id[e[i].to]) dfsup(e[i].to,cir[j],e[i].w);
        }
    }

//  for(int i=1;i<=n;i++) D(down[i]), E;
//  for(int i=1;i<=n;i++) D(up[i]), E;

    double ans=0;
    for(int i=1;i<=n;i++) ans+=(up[i]*fa[i]+down[i]*son[i])/(fa[i]+son[i]);
    printf("%.5f\n",ans/n);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值