题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4009
题目大意:
一个村庄发洪水,村民要搬到山上去,这就需要把水运上去。获得水资源有两种方式,第一,自己挖井,费用是海拔高度*x;第二,修水道引水,水道的费用是长度*Y,(长度=|x1 - x2|+|y1-y2| +|z1-z2|),另外,如果从比当前海拔低的人家引水,还有加Z这么多的钱,是水泵的费用。求让全村的人都能有水喝的最小费用!如果不能连通,输出-1。
解题思路:
1.由于海拔不同,修水道的费用不同,显然是有向图求最小费用。
2.最小树形图的根不确定,但是有题意可知,水是从山下来的,那么就新增一个虚点,作为水源(也就是根),设为所有房子的前驱,虚点与每个房子的权值为每个房子自己挖井的费用。
3.如果没有新设的根,所有的房子不一定都连通,注意审题,是让人们都能有水喝的费用,有可能每家挖个井是最便宜的。
-----------------------------以上题意思路摘自 妹子博客:http://blog.youkuaiyun.com/ac_lion/article/details/8104797--------------------------------
代码如下:
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
#define INF 0x3f3f3f3f
#define N 1001
struct point //点的结构体
{
int x,y,z;
}p[N];
struct edge //图的结构体
{
int u,v,w;
}e[N*N];
int eCnt,n,pre[N],id[N],in[N],visit[N];
//eCnt为图中的边数
//n为图中的顶点数
//pre[i]为顶点i的前驱节点
//id[i]为缩环,形成新图的中间量
//in[i]为点i的最小入边
//visit[i]遍历图时记录顶点是否被访问过
void add(int u,int v,int w)
{
e[eCnt].u=u;
e[eCnt].v=v;
e[eCnt++].w=w;
}
int directedMST(int root,int nv)
{
int ans=0;
while(1)
{
//1.找最小入边
for(int i=0;i<nv;i++) in[i]=INF;
for(int i=0;i<eCnt;i++)
{
int u=e[i].u;
int v=e[i].v;
if(u!=v&&e[i].w<in[v])
{
in[v]=e[i].w;
pre[v]=u;
}
}
for(int i=0;i<nv;i++) //判断图是否连通
if(i!=root&&in[i]==INF) return -1;
//2.找环
int nodeCnt=0; //图中环的数目
for(int i=0;i<nv;i++)
id[i]=visit[i]=-1;
in[root]=0;
for(int i=0;i<nv;i++)
{
ans+=in[i];
int v=i;
while(visit[v]!=i&&id[v]==-1&&v!=root)
{
visit[v]=i;
v=pre[v];
}
if(v!=root&&id[v]==-1)
{
for(int u=pre[v];u!=v;u=pre[u])
id[u]=nodeCnt;
id[v]=nodeCnt++;
}
}
if(nodeCnt==0) break;//如果无环,跳出循环
for(int i=0; i<nv; i++)
if(id[i]==-1)
id[i]=nodeCnt++;
//3.缩点,重新标记
for(int i=0;i<eCnt;i++)
{
int v=e[i].v;
e[i].u=id[e[i].u];
e[i].v=id[e[i].v];
if(e[i].u!=e[i].v)
e[i].w-=in[v];
}
nv=nodeCnt;
root=id[root];
}
return ans;
}
int X,Y,Z;
int Dis(point a,point b)
{
int dis=abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z);
dis*=Y;
if(a.z<b.z) dis+=Z;
return dis;
}
int main()
{
while(scanf("%d%d%d%d",&n,&X,&Y,&Z)!=EOF)
{
if(!n&&!X&&!Y&&!Z) break;
n++;
for(int i=1;i<n;i++)
scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
eCnt=0;
for(int i=1;i<n;i++)
{
int k;
scanf("%d",&k);
while(k--)
{
int t;
scanf("%d",&t);
if(t==i) continue;
add(i,t,Dis(p[i],p[t]));
}
add(0,i,p[i].z*X);
}
printf("%d\n",directedMST(0,n));
}
return 0;
}
关于朱刘算法,大部分都是这个经典图:
关于这个算法的缩点后的权值的改变为 (原图到某点的权值-到某点的最小入边),其实这个并不难想,首先要把所有的点连起来的最短路径肯定是构成一棵树,那么图中就没有环,那现在就要把有环的图的环的一条边删去(这样同样保证这个环上的所有点是联通的),再加入一条 环外 与这个环上某点相连通的边,这个边的权值就是原来的权值,但是当这条边加入以后,完全不需要环上某点的最小入度就可以保持环与外界的联通性,所以又需要减去某点的最小入度,故索性把原图到某点的权值-到某点的最小入边 定为新图的权值。
关于2,找环3,缩点,重新标记,自己在纸上写个1,2,3的环演练一下就知道了。。。