HDU 4009 Transfer water【最小树形图】

题目链接

题目意思

小A住在一个村庄,去年洪水淹没了这个村庄。因此他们决定将整个村庄搬到附近的山上。山上没有泉水,因此每一家必须选择打一个井或者从别的人家引水。如果一家决定去挖一口井,则他们打井的费用是他们房子的高度,X每米。如果决定去引水,如果引水的地方比当前地方高,则引水的钱是这两家的距离成上Y美元每米。如果引水的地方比当前低。则必须需要水泵(除了引水线后)还要花费Z美元买一个水泵。除此之外,村民的关系是需要考虑的。一些村民不允许一些村民从他们的房子引水。现在给出3个实数(a,b,c)。每个房子的坐标。计算整个村庄都通上水的最小花费。如果不能,则输出不能。

解题思路

这是一道最小树形图的题。因为每个点都可以选择打井或者是其他人家里引水,所以打井的位置是不能确定的。
这里就有一个巧妙的办法,我们建立一个人工节点0,将节点0到其他节点的权值赋成在节点打井的费用即可。要注意的是如果是从低处向高处引水,除了引水费用还要加上水泵的钱。
这样建立新图后就相当于求最小树形图。

代码部分
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int x,y,n,z;
struct node
{
    int x;
    int y;
    int z;
} p[1002];
struct Edge
{
    int u;
    int v;
    long long int w;
} edge[1002*1002];
long long int dis(node a,node b)
{
    long long int temp=abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z);
    return temp;
}
///closest[i]表示距离i最近的点,vis标记数组,in[i]存放节点的入边最小权值,id[i]存放节点的编号
int closest[1005],vis[1005],in[1005],id[1005];
int minroot;
long long int min_cost(int root,int nodenum,int edgenum)
{
    long long int ans=0;
    int i;
    while(true)
    {
        for(i=0; i<nodenum; i++)
            in[i]=INF;
        for(i=0; i<edgenum; i++)
        {
            int u=edge[i].u;
            int v=edge[i].v;
            ///u到v的权值如果比当前v的入边最小权还小,就更新最小权值,更新到v的最近的点为u
            if(u!=v&&edge[i].w<in[v])
            {
                in[v]=edge[i].w;
                closest[v]=u;
                //if(u==root)///某个点的前驱节点是虚拟节点
                //  minroot=i;
            }
        }
        for(i=0; i<nodenum; i++)
        {
            if(i==root)
                continue;
            if(in[i]==INF)
                return -1;
        }
        int cnt=0;
        memset(vis,-1,sizeof(vis));
        memset(id,-1,sizeof(id));
        in[root]=0;
        for(i=0; i<nodenum; i++)
        {
            ans+=in[i];
            int t=i;
            ///从t开始一直往前找,如果有环则会回到自身
            while(vis[t]!=i&&id[t]==-1&&t!=root)
            {
                vis[t]=i;
                t=closest[t];
            }
            if(t!=root&&id[t]==-1)
            {
                for(int u=closest[t]; u!=t; u=closest[u])
                {
                    id[u]=cnt;
                }
                id[t]=cnt++;
            }
        }
        if(cnt==0)
            break;
        for(i=0; i<nodenum; i++)///为不在环中的节点编号
        {
            if(id[i]==-1)
                id[i]=cnt++;
        }
        for(i=0; i<edgenum; i++)
        {
            int s=edge[i].u;
            int t=edge[i].v;
            edge[i].u=id[s];
            edge[i].v=id[t];
            if(s!=t)
                edge[i].w=edge[i].w-in[t];
        }
        nodenum=cnt;
        root=id[root];
    }
    return ans;
}
int main()
{
    int t,no;
    while(~scanf("%d%d%d%d",&n,&x,&y,&z))
    {
        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=0;
        for(int i=1; i<=n; i++)
        {
            edge[k].u=0;
            edge[k].v=i;
            edge[k].w=x*p[i].z;
            k++;
        }
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&t);
            for(int j=1; j<=t; j++)
            {
                scanf("%d",&no);
                edge[k].u=i;
                edge[k].v=no;
                edge[k].w=dis(p[i],p[no])*y;
                if(p[i].z<p[no].z)
                {
                    edge[k].w+=z;
                }
                k++;
            }
        }
        long long int ans=min_cost(0,n+1,k);
        if(ans<INF)
            printf("%lld\n",ans);
        else
            printf("poor XiaoA\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值