C市中有n个比较重要的地点,市长希望这些地点重点被考虑。现在可以修一些道路来连接其中的一些地点,每条道路可以连接其中的两个地点。另外由于C市有一条河从中穿过,也可以在其中的一些地点建设码头,所有建了码头的地点可以通过河道连接。
栋栋拿到了允许建设的道路的信息,包括每条可以建设的道路的花费,以及哪些地点可以建设码头和建设码头的花费。
市长希望栋栋给出一个方案,使得任意两个地点能只通过新修的路或者河道互达,同时花费尽量小。
接下来m行,每行三个整数a, b, c,表示可以建设一条从地点a到地点b的道路,花费为c。若c为正,表示建设是花钱的,如果c为负,则表示建设了道路后还可以赚钱(比如建设收费道路)。
接下来一行,包含n个整数w_1, w_2, …, w_n。如果w_i为正数,则表示在地点i建设码头的花费,如果w_i为-1,则表示地点i无法建设码头。
输入保证至少存在一个方法使得任意两个地点能只通过新修的路或者河道互达。
1 2 4
1 3 -1
2 3 3
2 4 5
4 5 10
-1 10 10 1 1
对于50%的数据,1<=n<=100,1<=m<=1000,-50<=c<=50,w_i<=50;
对于70%的数据,1<=n<=1000;
对于100%的数据,1 <= n <= 10000,1 <= m <= 100000,-1000<=c<=1000,-1<=w_i<=1000,w_i≠0。
这是一道求最小生成树的题,先说一下要注意的地方;一、两个城市之间有多条路径;二、由于是花费最小,所以每一条负边必须加入。
解题思路:
对于本题我用的是prim算法,由于有码头,所以这不是一道单纯的求最小生成树问题。我们可以对码头的花费进行抽象,进而转变为边的代价。抽象时分两种情况:一、两个城市均未构建码头,这是边的代价为两个城市码头的花费之和;二、其中一个城市构建了码头,即表示此城市已经被加入到已知组,这时边的代价为另一个未构建码头城市,构建码头的花费。由此可以看出,这样抽象的得到的边的是动态变化的,因为构建码头,会影响边的代价,而prim算法是针对静态的边,所以我们要对此做一些分析、操作,然后将其转换成静态的边。分析:当一个码头都没建立时,边的代价是静态的;当建立一个码头之后(公路的最小花费大于两个码头的最小花费),边的代价也是静态的。所以,要在这两状态的进行转化时,做一些操作,从而达到花费最小;处理:当建立第一个码头后,会发现一些已经加入的城市(可以建立码头,但没有建立,反而建立了公路),其连接下一城市的边(公路)的代价,大于建立码头的代价(因为当时建立两个码头的代价大于公路的代价),这时,就要将此公路拆除,替换为码头。修正这些城市之后,就可以继续使用prim算法了。
运行结果:使用的是java,只得了70
代码:
package bulebrige;
import java.util.ArrayList;
import java.util.Scanner;
public class BuildCity {
/**
* type:0表示公路 1表示水路,即修码头
* fee:表示所有的花费
* builded:表示是否建立了第一个码头
* list:用来保存与每个节点连接的公路
* wharfs:用来保存可以建立码头的城市编号
* nodes:用来保存每个未加入的城市编号
*/
private static int n,m,type=-1,fee=0,builded=0;
private static CityNode citys[];
private static ArrayList<CityEdge> list;
private static ArrayList<Integer> wharfs,nodes;
public static void main(String[] args) {
// TODO 自动生成的方法存根
wharfs=new ArrayList<Integer>();
nodes=new ArrayList<Integer>();
//
int time=0,a,b,price,vex=0,oprice = 0,tempprice=0,vexprice=Integer.MAX_VALUE,vexpre;
CityEdge edge1,edge2,edget;
Scanner read=new Scanner(System.in);
String str=read.nextLine();
String strr[]=str.split(" ");
n=Integer.valueOf(strr[0]);
m=Integer.valueOf(strr[1]);
citys=new CityNode[n];
while(time<m){
str=read.nextLine();
strr=str.split(" ");
a=Integer.valueOf(strr[0]);
b=Integer.valueOf(strr[1]);
price=Integer.valueOf(strr[2]);
edge1=new CityEdge(a-1,b-1,price);
edge2=new CityEdge(b-1,a-1,price);
if(citys[a-1]==null){
citys[a-1]=new CityNode();
citys[a-1].vex=a-1;
}
if(citys[b-1]==null){
citys[b-1]=new CityNode();
citys[b-1].vex=b-1;
}
citys[a-1].add(edge1);
citys[b-1].add(edge2);
time++;
//由于是求花费最小,所以负边一定要有,从数据看两个城市之间有多条路径
if(price<0){
fee+=price;
}
}
str=read.nextLine();
strr=str.split(" ");
int tempw=0;
for(int i=0;i<strr.length;i++){
tempw=Integer.valueOf(strr[i]);
if(tempw>0){
wharfs.add(i);
}
if(citys[i]==null){
citys[i]=new CityNode();
citys[i].vex=i;
}
nodes.add(i);
citys[i].wharfPrice=Integer.valueOf(strr[i]);
}
read.close();
// 找出每一条最短的路径,并加入
citys[vex].price=0;
for(int i=0;i<n;i++){
//出去重复的绝对值最大的负边
if(citys[vex].price<0){
fee-=citys[vex].price;
}
fee+=citys[vex].price;
//修建码头
if(citys[vex].type==1){
citys[vex].isWharfed=true;
vexpre=citys[vex].pre;
citys[vexpre].known=true;
citys[vexpre].isWharfed=true;
citys[vex].known=true;
if(builded==0){
//重构,去掉花费过多的公路,用码头代替
reseAffectbytMarf();
builded++;
}
}else if(citys[vex].type==0){
//修建公路,这时要保存一些信息,以便一次重构
vexpre=citys[vex].pre;
citys[vexpre].nextprice=citys[vex].price;
}
citys[vex].known=true;
list=citys[vex].getEdges();
//改变被影响的城市的值,在公路方面
for(int q=0;q<list.size();q++){
edget=list.get(q);
if(citys[edget.vex2].known==false&&citys[edget.vex2].price>edget.price){
citys[edget.vex2].price=edget.price;
citys[edget.vex2].type=0;
citys[edget.vex2].pre=vex;
}
}
//改变被影响的城市的值,在水路方面
//求水路的最小边,oprice用来抽象码头的花费,将码头的花费抽象为边的代价,两种情况:
//1当两个城市都没有码头时,边的代价为两个城市码头费用之和
//2当其中一个有码头时,边的代价为另一个码头花费
oprice=0;
int remove=-1;
if(citys[vex].wharfPrice>=0){
if(!citys[vex].isWharfed){
oprice=citys[vex].wharfPrice;
}
int num;
for(int j=0;j<wharfs.size();j++){
num=wharfs.get(j);//num为码头编号
if(citys[num].known==false){
//tempprice为抽象的边的代价
tempprice=oprice+citys[num].wharfPrice;
if(citys[num].price>tempprice){
citys[num].price=tempprice;
citys[num].pre=vex;
citys[num].type=1;
}
}else if(num==vex){
remove=j;
}
}
//移除已经加入的城市
wharfs.remove(remove);
}
//求出最小的边
vexprice=Integer.MAX_VALUE;
int removep=-1;
for(int p=0;p<nodes.size();p++){
if(citys[nodes.get(p)].known==false&&citys[nodes.get(p)].price<vexprice){
vexprice=citys[nodes.get(p)].price;
type=citys[nodes.get(p)].type;
vex=nodes.get(p);
removep=p;
}
}
if(removep>-1){
//移除下一次将要被加入的城市
nodes.remove(removep);
}
}
System.out.println(fee);
}
private static void reseAffectbytMarf(){
for(int i=0;i<n;i++){
if(citys[i].known&&citys[i].wharfPrice>0&&citys[i].isWharfed==false){
if(citys[i].nextprice>citys[i].wharfPrice){
fee-=citys[i].nextprice;
fee+=citys[i].wharfPrice;
}
}
}
}
}
/**
*
* @author Administrator
*vex:城市的编号
*wharfPrice:码头的花费
*price:要将自己加入时,需要的花费,即边的代价
*type:表示城市是通过水路连接还是公路连接
*pre:表示当前城市由那个城市连接,即边的另一端
*nextprice:当前城市连接一个未加入城市时,如果是公路连接,则记录这个边的代价,以便重构
*/
class CityNode{
int vex,wharfPrice,price=Integer.MAX_VALUE,type,pre,nextprice=-1;
boolean known=false,isWharfed=false;
ArrayList<CityEdge> edges=new ArrayList<CityEdge>();
public void add(CityEdge edge){
edges.add(edge);
}
public ArrayList<CityEdge> getEdges(){
return edges;
}
}
class CityEdge{
int vex1,vex2,price;
public CityEdge(int vex1,int vex2,int price){
this.vex1=vex1;
this.vex2=vex2;
this.price=price;
}
}