题目链接:
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\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; }