树形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]).

如下图所示:


以右边点集为子树求出左边点集中每个点作为树根时到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
",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
",ans);
        }
        printf("%.4f
",ans*1.0/q);
    }
   return 0;
}



原文地址:https://www.cnblogs.com/james1207/p/3341575.html