HDU4126 最小生成树+树形dp

来源:互联网 发布:iphone上看小说的软件 编辑:程序博客网 时间:2024/05/17 04:51

题目大意

战争时期有一座城市,给出地图,我们希望选出一些路来安置守卫,使得这些路可以连通所有城市,但是每条路安置守卫的费用是不同的。
可是由于形式多变,有些路上安置守卫的费用可能增加。我列了一张清单,共q种情况,每一种情况表示i城市到j城市之间道路上安放守卫的费用可能增加到w。我希望算出所有情况安置守卫的最小费用的平均值。

题目分析

首先用prim算法求出最小生成树并建树。
如果某种情况下要改变的边不在最小生成树中,那么此种情况下的最小费用就是最小生成树对吧。
那如果在呢?
我们先从最小生成树中删掉这条边,使得原来的最小生成树变成两棵树T1和T2,更改此边权值后,我们只要再在T1和T2之间加上一条边即可形成新的最小生成树了!
怎么证明呢?因为假设我们在T1或者T2中也要作修改,那么先加上一条边[i,j],则[i,j]与某些边会形成一个环,我们拿掉环里任意一条边,这就是一次修改。可是,拿掉的边的边权一定是小于[i,j]的,不然的话初始最小生成树中[i,j]肯定是被算进去了的,所以得证啦啦啦(≧▽≦)
好的,那么我们为了O(1)查询的伟大目标,来最小生成树上做一些预处理吧QWQ
我们可以想到,虽然情况有很多,但是只有修改最小生成树上的边才是有效修改,那么为什么不预处理所有边去掉后的代替边最小费用呢?
首先,我们预处理一下dp[i][j]:我们把点i作为根节点之后,点i到以点j为根的子树之间连一条新边的最小费用。这个枚举i后dfs,可以O(n2)算出
然后,我们处理一下best[i][j]:i和j在最小生成树上之间有边,那么去掉这条边后,用于连接i这边的连通块和j这边的连通块的最小费用,这个只要枚举i这边连通块上每个点t的dp[t][j]和j这边连通块上每个点t的dp[t][i]就可以了!那么dfs完成吧,还是O(n2)
此题以完美解决,撒花~~~

代码

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<climits>using namespace std;#define LL long longconst int N=3005;int n,m,q,tot;double ans;LL mp[N][N],dp[N][N],best[N][N],inf=1e10;int h[N],ne[N<<1],to[N<<1];LL w[N<<1];void add(int x,int y,LL z){to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}//建树LL dis[N],sum;int vis[N],fa[N];void prim(){//最小生成树    int i,j,k,mx;    memset(h,-1,sizeof(h)),tot=0;    for(i=1;i<=n;++i)dis[i]=INT_MAX,vis[i]=0;    dis[1]=0,fa[1]=0;    for(i=1;i<=n;++i){        mx=inf,k=0;        for(j=1;j<=n;++j)            if(mx>dis[j]&&!vis[j])mx=dis[j],k=j;        vis[k]=1;if(k!=1)add(k,fa[k],dis[k]),add(fa[k],k,dis[k]),sum+=dis[k];        for(j=1;j<=n;++j)            if(!vis[j]&&mp[k][j]<dis[j])dis[j]=mp[k][j],fa[j]=k;    }}void dfs1(int rt,int x,int las){//求rt点到以x为根的子树最短距离(一条边)    for(int i=h[x];i!=-1;i=ne[i]){        if(to[i]==las)continue;dfs1(rt,to[i],x);        dp[rt][x]=min(dp[rt][to[i]],dp[rt][x]);    }    if(las!=rt)dp[rt][x]=min(dp[rt][x],mp[rt][x]);}LL dfs2(int rt,int x,int las){//求rt与以x为根的子树不连通后,rt与x重新连通的最短距离    LL re=dp[x][rt];    for(int i=h[x];i!=-1;i=ne[i]){        if(to[i]==las)continue;        re=min(re,dfs2(rt,to[i],x));    }    return re;}void work(){    int i,j,x,y;LL ww;    for(i=1;i<=n;++i)dfs1(i,i,-1);    for(i=1;i<=n;++i)//只有之间有边的i和j才要求best[i][j]啦        for(j=h[i];j!=-1;j=ne[j])        best[i][to[j]]=best[to[j]][i]=dfs2(i,to[j],i);    scanf("%d",&q);    for(i=1;i<=q;++i){        scanf("%d%d%lld",&x,&y,&ww);        ++x,++y;        if(fa[x]!=y&&fa[y]!=x)ans+=sum*1.0;        else ans+=sum*1.0-mp[x][y]*1.0+min(best[x][y],ww)*1.0;    }}int main(){    int i,j,x,y;LL ww;    while(1){        scanf("%d%d",&n,&m);        if(!n&&!m)break;        for(i=1;i<=n;++i)//初始化            for(j=1;j<=n;++j)best[i][j]=mp[i][j]=dp[i][j]=inf;        ans=0,sum=0;        for(i=1;i<=m;++i){            scanf("%d%d%lld",&x,&y,&ww);            ++x,++y;            mp[x][y]=mp[y][x]=ww;        }        prim(),work();        printf("%.4lf\n",ans/q*1.0);    }    return 0;}
原创粉丝点击