hdu4009 Transfer water(最小树形图模板)

题目链接:点击打开链接

题意描述:在一个村庄有n户人家(x,y,h),现在打算在村庄中挖井和建水渠使每户人家都可以用上水

1、如果挖井费用为高度h*X;

2、如果a->b建水渠,if(a.h>=b.h)费用为两点距离dis*Y,反之费用为dis*Y+Z

问,怎样挖井和建水渠才能使费用最小?


解体思路:最小树形图:点击打开链接

分析:我们可以新建一个源点指向所有的用户,权值为挖井的费用,然后根据是否能建水渠构建个用户之间的关系,求图的最小树形图即可

代码:

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#define MAXN 1010
#define INF 0x7fffffff
using namespace std;
struct Point{
    int x,y,z;
}p[MAXN];
struct Edge{
    int u,v,cost;
    Edge(){}
    Edge(int u,int v,int cost):u(u),v(v),cost(cost){}
}edge[MAXN*MAXN];
int n,m,X,Y,Z;
inline int get_cost(Point a,Point b){
    int dis=abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z);
    if(a.z>=b.z) return dis*Y;
    return dis*Y+Z;

}
int pre[MAXN];///记录当前顶点的前驱顶点
int id[MAXN];///经过缩点之后:原图顶点号->新图顶点号
int in[MAXN];///记录顶点所有入边的权值的最小值
int vis[MAXN];///是否访问过当前顶点
inline int ZhuLiu(int root,int n,int m){
    int u,v;
    int ret=0;///最小树形图的权值
    while(true){
        for(int i=0;i<=n;++i) in[i]=INF;
        /*记录顶点所有入边的权值的最小值*/
        for(int i=0;i<m;++i){
            u=edge[i].u; v=edge[i].v;
            if(edge[i].cost<in[v]&&u!=v){///去除v->v这种入边
                pre[v]=u;
                in[v]=edge[i].cost;
            }
        }
        /*除根节点外其他顶点每个点都必须有入边,如果没有则无法构成树,返回INF表示不存在*/
        for(int i=0;i<=n;++i){
            if(i==root) continue;
            if(in[i]==INF) return INF;
        }
        in[root]=0;
        int cnt=0;///对每个顶点进行重新编号
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        for(int i=0;i<=n;++i){
            ret+=in[i];
            v=i;
            /*构造环*/
            while(vis[v]!=i&&id[v]==-1&&v!=root){
                vis[v]=i;
                v=pre[v];
            }
            /*判断是否存在环,如果存在环则进行缩点,环中每一个顶点对应同一个顶点cnt*/
            if(v!=root&&id[v]==-1){
                for(u=pre[v];u!=v;u=pre[u]) id[u]=cnt;
                id[v]=cnt++;
            }
        }
        /*如果不存在环,则当前树即为最小树形图*/
        if(cnt==0) break;
        /*对于其他不在环中的顶点进行编号*/
        for(int i=0;i<=n;++i){
            if(id[i]==-1) id[i]=cnt++;
        }
        /*对相应的边进行修改*/
        for(int i=0;i<m;++i){
            v=edge[i].v;
            edge[i].u=id[edge[i].u];
            edge[i].v=id[edge[i].v];
            if(edge[i].u!=edge[i].v)
                edge[i].cost-=in[v];
        }
        n=cnt-1;
        root=id[root];
    }
    return ret;
}
int main(){
    while(scanf("%d%d%d%d",&n,&X,&Y,&Z)!=EOF){
        if(n==0&&X==0&&Y==0&&Z==0) break;
        for(int i=1;i<=n;++i) scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
        int k,to;m=0;
        for(int i=1;i<=n;++i){
            scanf("%d",&k);
            while(k--){
                scanf("%d",&to);
                edge[m++]=Edge(i,to,get_cost(p[i],p[to]));
            }
        }
        for(int i=1;i<=n;++i)
            edge[m++]=Edge(0,i,p[i].z*X);
        printf("%d\n",ZhuLiu(0,n,m));
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值