安慰奶牛

问题描述

Farmer John变得非常懒,他不想再继续维护供奶牛之间供通行的道路。道路被用来连接N个牧场,牧场被连续地编号为1到N。每一个牧场都是一个奶牛的家。FJ计划除去P条道路中尽可能多的道路,但是还要保持牧场之间的连通性。你首先要决定那些道路是需要保留的N-1条道路。第j条双向道路连接了牧场Sj和Ej(1 <= Sj <= N; 1 <= Ej <= N; Sj != Ej),而且走完它需要Lj的时间。没有两个牧场是被一条以上的道路所连接。奶牛们非常伤心,因为她们的交通系统被削减了。你需要到每一个奶牛的住处去安慰她们。每次你到达第i个牧场的时候(即使你已经到过),你必须花去Ci的时间和奶牛交谈。你每个晚上都会在同一个牧场(这是供你选择的)过夜,直到奶牛们都从悲伤中缓过神来。在早上起来和晚上回去睡觉的时候,你都需要和在你睡觉的牧场的奶牛交谈一次。这样你才能完成你的交谈任务。假设Farmer John采纳了你的建议,请计算出使所有奶牛都被安慰的最少时间。

 

输入格式

第1行包含两个整数N和P。

 

接下来N行,每行包含一个整数Ci。

 

接下来P行,每行包含三个整数Sj, Ej和Lj。

 

输出格式

输出一个整数, 所需要的总时间(包含和在你所在的牧场的奶牛的两次谈话时间)。

样例输入

5 6

10

10

20

6

30

1 2 5

2 3 5

2 4 12

3 4 17

2 5 15

3 5 5

4 5 12

样例输出

176

 

         整理下思路:

1.      每加多一个牧场,要安慰那里的奶牛所画的时间为Sj+Ej+2Lj,因为你要去那里,然后再回来;

2.      最后都要回来睡觉,那么就是选最小时间花费的点为根的最小生成树。

 

有了思路,接下来就是优化算法了:

我写了两个算法的优化:

1.      Prim算法,这个算法是把已加结点中与没有加进来的结点连接的所有边中选取最小的加进来直到所有点都加进来为止。考虑到每新加进来一个点时,只是把原本那些边的的集合删掉一个最小边,加进一些新加点与没有加进来的结点连接的边,然后在加下一个结点在选取最小边这样的性质,我把这些边用一个最小堆存放,这样能够大大地加速每次最小边的选取速度;

2.      Kruskal算法,这个算法是从边集上入手,从最小边开始加,如果边的两个端点不在一个连通分区内,就加进来,直到加进N-1条边为止。这里我加速的地方时判断两个端点是否在一个连通分区内,用了并查集的方法。

 

 

Prim算法的java代码:

import java.util.Scanner;

public class comfort{
	private static int b[],c[][],heapn=1;
	private static Node[] root,temp;//使用链表存储图的连通状态
	private static Heap[] h;

	public void setRoot(int n) {
		this.root =new Node[n];
	}

	public void setTemp(int n) {
		this.temp =new Node[n];
	}
	
    public void setH(int n) {
		this.h =new Heap[n];
	}

	public static void main(String[] args) 
    {
    	Scanner sc=new Scanner(System.in);
    	int n,m,i,x,y,l,sum=0,min=Integer.MAX_VALUE;
    	comfort co=new comfort();
    	Node t;
    	n=sc.nextInt();
    	co.setRoot(n);
    	co.setTemp(n);
    	b=new int[n];
    	c=new int[n][2];
    	b[0]=0;
    	m=sc.nextInt();
    	co.setH(m*2);
    	for(i=0;i<n;i++)
    	{
    		c[i][0]=sc.nextInt();
			root[i]=co.new Node();
			temp[i]=co.new Node();
			temp[i]=root[i];
    		if(min>c[i][0])min=c[i][0];
    	}
    	for(i=0;i<m;i++)
    	{
    		x=sc.nextInt()-1;
			y=sc.nextInt()-1;
			l=sc.nextInt();
			l=l*2+c[x][0]+c[y][0];
			t=co.new Node();t.n=x;t.cost=l;
			temp[y].next=t;temp[y]=t;
			t=co.new Node();t.n=y;t.cost=l;
			temp[x].next=t;temp[x]=t;
    	}
    	for(t=root[0].next;t!=null;t=t.next)//丢进加入第一个点时它所有的边作为堆的初始化
    	{
    		h[heapn]=co.new Heap();h[heapn].x=0;h[heapn].y=t.n;h[heapn].cost=t.cost;
    		m=heapn;
    		heapn++;
    		while(true)
    		{
    			if(m==1)break;
    			if(h[m].cost<h[m/2].cost)
    			{
    				x=0;y=t.n;l=t.cost;
    				h[m].x=h[m/2].x;h[m].y=h[m/2].y;h[m].cost=h[m/2].cost;
    				h[m/2].x=x;h[m/2].y=y;h[m/2].cost=l;
    				m=m/2;
    			}
    			else break;
    		}
    	}
		for(i=0;i<n-1;i++)
		{
			sum+=f(b[i],i+1);
			if(i==n-2)break;
			co.del();//点加进来后最小边丢弃
			for(t=root[b[i+1]].next;t!=null;t=t.next)
	    	{
				if(c[t.n][1]==1)continue;
	    		h[heapn]=co.new Heap();h[heapn].x=b[i+1];h[heapn].y=t.n;h[heapn].cost=t.cost;
	    		m=heapn;
	    		heapn++;
	    		while(true)
	    		{
	    			if(m==1)break;
	    			if(h[m].cost<h[m/2].cost)
	    			{
	    				x=b[i+1];y=t.n;l=t.cost;
	    				h[m].x=h[m/2].x;h[m].y=h[m/2].y;h[m].cost=h[m/2].cost;
	    				h[m/2].x=x;h[m/2].y=y;h[m/2].cost=l;
	    				m=m/2;
	    			}
	    			else break;
	    		}
	    	}
		}
    	System.out.println(sum+min);
    }

	//确定第i个点是多少,返回最小边的花费
	private static int f(int i,int num) {
		// TODO Auto-generated method stub
		c[i][1]=1;
		while(true)
		{
			if(c[h[1].x][1]==1&c[h[1].y][1]==1)del();//特殊情形,最小边两个端点在已选结点的集合内
			else break;
		}
		if(c[h[1].x][1]==1)b[num]=h[1].y;
		else b[num]=h[1].x;
		return h[1].cost;
	}

	//堆的最小边的删除
	private static void del() {
		// TODO Auto-generated method stub
		int x=h[heapn-1].x,y=h[heapn-1].y,cost=h[heapn-1].cost,n=1;
		h[heapn-1].cost=Integer.MAX_VALUE;
		while(true)
		{
			if(h[2*n]==null)break;
			else if(h[2*n+1]==null||h[2*n].cost<h[2*n+1].cost)
			{
				if(h[2*n].cost<cost)
				{
					h[n].x=h[2*n].x;h[n].y=h[2*n].y;h[n].cost=h[2*n].cost;
					n=2*n;
				}
				else break;
			}
			else
			{
				if(h[2*n+1].cost<cost)
				{
					h[n].x=h[2*n+1].x;h[n].y=h[2*n+1].y;h[n].cost=h[2*n+1].cost;
					n=2*n+1;
				}
				else break;
			}
		}
		h[n].x=x;h[n].y=y;h[n].cost=cost;
		heapn--;
		return;
	}

	class Node{	
		public int n,cost;
		public Node next=null;
	}
	class Heap{
		public int x,y,cost;
	}
}

Kruskal算法的Java代码:

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class comfortTwo{
	private static int c[][];
	private static Node temp[];
	
    public static void setTemp(int n) {
		temp =new Node[n];
	}
    
	public static void main(String[] args) 
    {
    	Scanner sc=new Scanner(System.in);
    	int n,m,i,x,y,l,sum=0,min=Integer.MAX_VALUE,link=0;
    	comfortTwo co=new comfortTwo();
    	Node t;
    	n=sc.nextInt();
    	c=new int[n][2];
    	m=sc.nextInt();
    	co.setTemp(m);
    	for(i=0;i<n;i++)
    	{
    		c[i][0]=sc.nextInt();
    		c[i][1]=i;
    		if(min>c[i][0])min=c[i][0];
    	}
    	for(i=0;i<m;i++)
    	{
    		x=sc.nextInt()-1;
			y=sc.nextInt()-1;
			l=sc.nextInt();
			temp[i]=co.new Node();
			temp[i].x=x;temp[i].y=y;temp[i].cost=l=l+l+c[x][0]+c[y][0];
    	}
    	Arrays.sort(temp,co.new com());//把边由小到大排序
    	for(i=0;link+1<n;i++)//不断加边,直到边的数量到n-1
    	{
    		x=temp[i].x;y=temp[i].y;
    		while(x!=c[x][1])x=c[x][1];//并查集的实现
    		while(y!=c[y][1])
    		{
    			l=y;
    			y=c[y][1];
    			c[l][1]=x;
    		}
    		if(x==y)continue;
    		else
    		{
    			c[y][1]=x;
    			sum+=temp[i].cost;
    			link++;
    		}
    	}
    	System.out.println(sum+min);
    }
	
	class com implements Comparator<Node> {
	    public int compare(Node arg0, Node arg1) {
	    	Node t1=(Node)arg0;
	    	Node t2=(Node)arg1;
	        if(t1.cost == t2.cost)
	        	return 0;	            
	        else
	            return t1.cost>t2.cost? 1:-1;
	    }
	}
	
	class Node{	
		public int x,y,cost;
	}
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值