HDU4126 最小生成树+树形dp

题目大意

战争时期有一座城市,给出地图,我们希望选出一些路来安置守卫,使得这些路可以连通所有城市,但是每条路安置守卫的费用是不同的。
可是由于形式多变,有些路上安置守卫的费用可能增加。我列了一张清单,共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 long
const 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值