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

题目:一个村庄发洪水,村民要搬到山上去,这就需要把水运上去。获得资源有两种方式,第一,自己挖井,费用是海拔高度*x第二修水道引水,水道的费用是长度*Y,(长度=|x1 - x2|+|y1-y2| +|z1-z2|),另外,如果从比当前海拔低的人家引水,还有加Z这么多的钱,是水泵的费用。求让全村的人都能有水喝的最小费用!如果不能连通,输出-1

分析:第一,由于海拔不同,修水道的费用不同,显然是有向图求最小费用;

           第二,最小树形图的根不确定,但是有题意可知,水是从山下来的,那么久新增一个点,作为水源(也就是根)设为所有房子的前驱。

          第三,要明确,如果没有新设的根,所有的房子不一定都连通,注意审题,是让人们都能有水喝的费用,有可能每家挖个井是最便宜的。

代码如下

#include <cstdio>
#include <cstring>
#include <cstdlib>
#define INF 0x3f3f3f3f
#define MAX 1001
struct node{
        int u, v, w;
}edge[MAX*MAX];
int n, x, y, z, es, k, tt;
int pre[MAX], in[MAX], id[MAX], vis[MAX];
int pnt[MAX][3];

void addedge( int u, int v, int w ) {
        edge[es].u = u, edge[es].v = v, edge[es++].w = w;
}
int dist( int i, int j ) {
        return abs( pnt[i][0] - pnt[j][0] ) + abs( pnt[i][1] - pnt[j][1] ) + abs( pnt[i][2] - pnt[j][2] );
}
int dirMst( int root, int vs ) {
        int ans = 0;
        while(1) {
                memset( in, INF, sizeof(in) );
                memset( id, -1, sizeof(id) );
                memset( vis, -1, sizeof(vis) ); 
                for ( int i = 0; i < es; ++i ) {
                        int u = edge[i].u, v = edge[i].v, w = edge[i].w;
                        if ( w < in[v] && v != u ) pre[v] = u, in[v] = w;
                }//求最小入边集
           in[root] = 0, pre[root] = root;//为之后的遍历做准备,in【】用于存储最小边集
                for ( int i = 0; i < vs; ++i ) {
                        if ( in[i] == INF ) return -1;
                        ans += in[i];
                }//此循环判断是否有孤立节点,同时计算当前最小权值和
                int idx = 0;//idx是用来给新的节点赋名称的(新序号)
                for ( int i = 0; i < vs; ++i )
                        if ( vis[i] == -1 ) {
                                int u = i;
                                while ( vis[u] == -1 ) vis[u] = i, u = pre[u];//将vis[u]赋值为i是为了做标记
                                if ( vis[u] != i || u == root ) continue;//判断是否形成环
                                for ( int t = pre[u]; t != u; t = pre[t] ) id[t] = idx;//给环上的点赋值一个统一的标号,形成一个新的点(id[i]用于存储i点的新标号)
                                id[u] = idx++;
                        }
                if ( idx == 0 ) break;//没有环,跳出循环
                for ( int i = 0; i < vs; i++ ) if ( id[i] == -1 ) id[i] = idx++;//在上一个循环给环赋予新的序号,这一步是给非环的点新的序号,保证新图中每个点有相应的标号
                for ( int i = 0; i < es; i++ ) {
                        edge[i].w -= in[edge[i].v];//把每个点的其他入边看做环的入边,将增值赋给环的入边,作为新图的这个新的点的该条入边的权值
                        edge[i].u = id[edge[i].u];
                        edge[i].v = id[edge[i].v];
                }//这个循环是修改旧图,把id中存储的映射到图中形成新图
                vs = idx;//这个很重要,要知道把环捏成一个点,那么图中点个数改变了,所以循环次数改变
                root = id[root];//给根新的标号
        }
        return ans;
}

int main()
{
        while ( scanf( "%d%d%d%d", &n, &x, &y, &z ) != EOF && ( n || x || y || z ) ) {
                for ( int i = 1; i <= n; ++i )
                        scanf("%d%d%d", &pnt[i][0], &pnt[i][1], &pnt[i][2]);
                es = 0;
                for ( int i = 1; i <= n; ++i ) addedge( 0, i, pnt[i][2] * x );
                for ( int i = 1; i <= n; ++i ) {
                        scanf("%d", &k);
                        while(k--) {
                                int tt, linec;
                                scanf("%d", &tt);
                                linec = dist( i, tt );
                                if ( pnt[i][2] >= pnt[tt][2] ) addedge( i, tt, linec*y );
                                else addedge( i, tt, linec*y + z );
                        }
                }
                        int ans = dirMst(0, n + 1);
                        if ( ans == -1 ) printf("poor XiaoA\n");
                        else printf("%d\n", ans);
        }
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值