树形dp+MST-hdu-4126-Genghis Khan the Conqueror

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=4126

题目意思:

给一图,n个点,m条边,每条边有个花费,给出q条可疑的边,每条边有新的花费,每条可疑的边出现的概率相同,求不能经过原来可疑边(可以经过可疑边新的花费构建的边),注意每次只出现一条可疑的边,n个点相互连通的最小花费的期望。

解题思路:

树形dp+MST。

先用kruskal算法找到最小生成树,并求出总花费sum.

再以枚举n个点,依次作为树根dfs,dp[i][j]表示<i,j>为最小生成树上的边,且去掉该边后,包括点i的连通块中的点集A到包括点j的连通块点集B的最小距离。

对于根节点为ro,边为<i,j>的dp[i][j]=min(以j节点为根的子树到ro的最短距离,dp[i][j]).

如下图所示:

树形dp+MST-hdu-4126-Genghis Khan the Conqueror

以右边点集为子树求出左边点集中每个点作为树根时到all的最小距离。其实对于上面那条边,只用枚举ro个点就行了,但是不好确定每条边的ro集,所以枚举n个点,作为ro,然后dfs,最每条边更新一次,时间复杂度为o(n^2)可以接受。

代码:

 

#include<iostream>

#include<cmath>

#include<cstdio>

#include<cstdlib>

#include<string>

#include<cstring>

#include<algorithm>

#include<vector>

#include<map>

#include<set>

#include<stack>

#include<list>

#include<queue>

#include<ctime>

#define eps 1e-6

#define INF 0x3f3f3f3f

#define PI acos(-1.0)

#define ll __int64

#define lson l,m,(rt<<1)

#define rson m+1,r,(rt<<1)|1

#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;



#define Maxn 3300

struct Edge

{

    int a,b,c;

}edge[Maxn*Maxn]; //保存边的信息



int dis[Maxn][Maxn]; //原始距离

bool hav[Maxn][Maxn]; //是否为最小生成树上的边

int fa[Maxn],dp[Maxn][Maxn];//dp[i][j]表示<i,j>为最小生成树上的边,且去掉该边后,包括点i的连通块中的点集A到包括点j的连通块点集B的最小距离。

int n,m,cnt;

ll sum;



int find(int x) //并查集

{

    int tmp=x;

    while(x!=fa[x])

        x=fa[x];

    while(fa[tmp]!=x)

    {

        int tt=fa[tmp];

        fa[tmp]=x;

        tmp=tt;

    }

    return x;

}

bool cmp(struct Edge a,struct Edge b)

{

    return a.c<b.c;

}

struct EE //构建最小生成树

{

    int v;

    struct EE * next;

}ee[Maxn<<1],*head[Maxn<<1];



void add(int a,int b)

{

    ++cnt;

    ee[cnt].v=b;

    ee[cnt].next=head[a];

    head[a]=&ee[cnt];

}



void kruskal() //克鲁斯卡尔算法求最小生成树

{

    sum=0;

    cnt=0;

    for(int i=1;i<=m;i++)

    {

        int a=find(edge[i].a),b=find(edge[i].b);

        if(a!=b)

        {

            fa[b]=edge[i].a;

            sum+=edge[i].c;

            hav[edge[i].a][edge[i].b]=hav[edge[i].b][edge[i].a]=true;

            add(edge[i].a,edge[i].b);  //建树

            add(edge[i].b,edge[i].a);

        }

    }

}

int dfs(int ro,int fa,int cur,int dep) //表示以cur作为当前子树根中所有子树节点到总根ro的最短距离

{

    struct EE * p=head[cur];

    int mi=INF;



    if(dep!=1) //不为树根的儿子

        mi=dis[cur][ro];

    while(p)

    {

        int v=p->v;

        if(v!=fa)

        {

            int tt=dfs(ro,cur,v,dep+1);

            mi=min(mi,tt);

            dp[cur][v]=dp[v][cur]=min(dp[v][cur],tt);//更新当前边

        }

        p=p->next;

    }

    return mi;



}



int main()

{

   // printf("%d\n",INF);

    while(scanf("%d%d",&n,&m)&&n+m)

    {

        memset(dis,INF,sizeof(dis));

        for(int i=1;i<=m;i++)

        {

            int a,b,c;

            scanf("%d%d%d",&a,&b,&c);

            edge[i].a=a,edge[i].b=b,edge[i].c=c;

            dis[a][b]=dis[b][a]=c;

        }

        sort(edge+1,edge+m+1,cmp);

        for(int i=0;i<n;i++)

            fa[i]=i;

        memset(hav,false,sizeof(hav));

        memset(head,NULL,sizeof(head));

        kruskal();



        memset(dp,INF,sizeof(dp));

        for(int i=0;i<n;i++)  //以每个点最为树根,对每条边更新n次

            dfs(i,i,i,0);



        ll ans=0;

        int q;

        scanf("%d",&q);

        for(int i=1;i<=q;i++)

        {

            int a,b,c;

            scanf("%d%d%d",&a,&b,&c);

            if(hav[a][b]) //是最小生成树上的边

            {

                int tt=min(dp[a][b],c); //要么用新边,要么用不是最小生成树上的边

                ans=ans+sum-dis[a][b]+tt;

            }

            else //不是最小生成树上的边,直接用最小生成树

                ans=ans+sum;

           // printf("*%lf\n",ans);

        }

        printf("%.4f\n",ans*1.0/q);

    }

   return 0;

}


 


 

你可能感兴趣的:(ror)