【HDU4009 Transfer water】 最小树形图模板

这是一道最小树形图的模板题。不清楚朱刘算法的话可以看看这篇博客,写的比较形象。

题意

计算让每家都喝上水的最小花费。每家可以挖水井或者从有水的人家引水(一些家庭可能不允许其他一些家庭从那里建一条水线)。水井的花费为该户人家的高度乘X,水线的花费为两户人家的曼哈顿距离乘Y(供水高度低于取水高度还需要加Z购买水泵)。

思路

我们可以将可以建立水线的关系看作是一个带权有向图。构建一个零点,与各点的权值即为打井的花费。因为我们必然需要至少一口井,所以我们可以将这个零点视为根节点。然后套用我们的最小树形图的模板即可。

模板

#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <functional>
using namespace std;

typedef long long ll;

const int MAXN = 1005;
const int INF = 1e9+7;

typedef struct edge{
    int u;
    int v;
    int n;
}edge;

edge e[MAXN*MAXN];

int in[MAXN]; //记录最小入边权
int pre[MAXN]; //记录该点入边的起点
int vis[MAXN];
int id[MAXN]; //记录该点属于哪个环

bool cmp(edge a,edge b){
    return a.n<b.n;
}

int zhuliu(int n,int m,int root){ //n是点数,m是边数,root是根节点
    sort(e+1,e+m+1,cmp); //将边从小到大排序
    int sum=0;
    while(1){
        for(int i=0;i<=n;++i){ 
            in[i]=INF; //初始化为无穷大
            pre[i]=i;
        }
        for(int i=1;i<=m;++i){ //遍历每条边,寻找每个点的最小入边
            if(e[i].v==e[i].u) continue;
            if(e[i].n<in[e[i].v]){
                in[e[i].v]=e[i].n;
                pre[e[i].v]=e[i].u;
            }
        }
        for(int i=0;i<=n;++i){ //遍历每个点,除根节点外如果有点无法找到一条入边,说明无法构成最小树形图
            if(i==root) continue;
            if(in[i]==INF) return -1;
        }
        in[root]=0;
        int cnt = -1; //环的编号
        for(int i=0;i<=n;++i){ //初始化
            vis[i]=-1;
            id[i]=-1;
        }
        for(int i=0;i<=n;++i){
            sum+=in[i];
            int k = i;
            while(k!=root&&vis[k]!=i&&id[k]==-1){
                vis[k] = i;
                k = pre[k];
            }
            if(k!=root&&id[k]==-1){ //标记新的环
                id[k]=++cnt;
                for(int p = pre[k];p!=k;p=pre[p]) id[p]=cnt;
            }
        }
        if(cnt==-1) break; //没有环,退出循环
        //建立新图,缩点
        for(int i = 0;i<=n;++i){
            if(id[i]==-1) id[i]=++cnt;
        }
        for(int i=1;i<=m;++i){
            int k = e[i].v;
            e[i].u=id[e[i].u];
            e[i].v=id[e[i].v];
            e[i].n-=in[k];
        }
        root=id[root];
        n=cnt;
    }
    return sum;
}

AC代码

#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <functional>
using namespace std;

typedef long long ll;

const int MAXN = 1005;
const double EPS = 1e-8;
const int INF = 1e9+7;

typedef struct node{
    int x;
    int y;
    int z;
}node;

node s[MAXN];

typedef struct edge{
    int u;
    int v;
    int n;
}edge;

edge e[MAXN*MAXN];

int in[MAXN]; //记录最小入边权
int pre[MAXN]; //记录该点入边的起点
int vis[MAXN];
int id[MAXN]; //记录该点属于哪个环

bool cmp(edge a,edge b){
    return a.n<b.n;
}

int zhuliu(int n,int m,int root){ //n是点数,m是边数,root是根节点
    sort(e+1,e+m+1,cmp); //将边从小到大排序
    int sum=0;
    while(1){
        for(int i=0;i<=n;++i){ 
            in[i]=INF; //初始化为无穷大
            pre[i]=i;
        }
        for(int i=1;i<=m;++i){ //遍历每条边,寻找每个点的最小入边
            if(e[i].v==e[i].u) continue;
            if(e[i].n<in[e[i].v]){
                in[e[i].v]=e[i].n;
                pre[e[i].v]=e[i].u;
            }
        }
        /*
        //因为这道题必然有解,所以可以略过
        for(int i=0;i<=n;++i){ //遍历每个点,除根节点外如果有点无法找到一条入边,说明无法构成最小树形图
            if(i==root) continue;
            if(in[i]==INF) return -1;
        }
        */
        in[root]=0;
        int cnt = -1; //环的编号
        for(int i=0;i<=n;++i){ //初始化
            vis[i]=-1;
            id[i]=-1;
        }
        for(int i=0;i<=n;++i){
            sum+=in[i];
            int k = i;
            while(k!=root&&vis[k]!=i&&id[k]==-1){
                vis[k] = i;
                k = pre[k];
            }
            if(k!=root&&id[k]==-1){ //标记新的环
                id[k]=++cnt;
                for(int p = pre[k];p!=k;p=pre[p]) id[p]=cnt;
            }
        }
        if(cnt==-1) break; //没有环,退出循环
        //建立新图,缩点
        for(int i = 0;i<=n;++i){
            if(id[i]==-1) id[i]=++cnt;
        }
        for(int i=1;i<=m;++i){
            int k = e[i].v;
            e[i].u=id[e[i].u];
            e[i].v=id[e[i].v];
            e[i].n-=in[k];
        }
        root=id[root];
        n=cnt;
    }
    return sum;
}

int main(){
    int n,x,y,z;
    while(~scanf("%d %d %d %d",&n,&x,&y,&z)&&(n||x||y||z)){
        for(int i=1;i<=n;++i){
            scanf("%d %d %d",&s[i].x,&s[i].y,&s[i].z);
        }
        int p = 0;
        for(int i=1;i<=n;++i){
            int k;scanf("%d",&k);
            while(k--){
                int t;scanf("%d",&t);
                p++;e[p].u=i;e[p].v=t;
                e[p].n = y * (abs(s[i].x-s[t].x)+abs(s[i].y-s[t].y)+abs(s[i].z-s[t].z));
                if(s[i].z<s[t].z) e[p].n+=z;
            }
        }
        for(int i=1;i<=n;++i){ //添加零点
            p++;e[p].u=0;e[p].v=i;
            e[p].n=s[i].z*x;
        }
        int sum = zhuliu(n,p,0);
        printf("%d\n",sum);
    }
    return 0;
}
posted @ 2021-01-18 20:09  樱与梅子  阅读(91)  评论(0)    收藏  举报