【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;
}

浙公网安备 33010602011771号