题意:
n个城市供电,要求每个城市或自己建立发电站,或与建立了发电站的城市直接或间接相连。给出每个点的横纵坐标xi,yi,以及每个点的建站花费和连线参数wi,ci,如果两座城市i、j之间连线,则花费为(ci+cj)*(ij之间的曼哈顿距离)。要求求出最小花费和建站的城市的编号以及连线的城市编号对。
我一开始的想法是,先找出建站花费最小的点,建站,然后对所有点跑一遍prim。之后,如果有的点的建站花费小于它在这个生成树上的边的花费,那么把这条边的花费减掉,加上建站花费。
官方给出的题解是,建一个超级点,也就是虚拟原点,使它到每个城市的花费为每个城市建站的花费,其余普通点之间的花费为连线花费,然后跑一遍prim即可。
感觉这两个想法道理应该是一样的,也可能我的贪心并不能达到全局最优。以下为使用官方思路的AC代码:
public class A {
public static int maxn=2100;
public static long ans=0,e[][]=new long[maxn][maxn],INF=Long.MAX_VALUE;
public static long mincost[]=new long[maxn];
public static int root[]=new int[maxn];
public static boolean vis[]=new boolean[maxn];
public static void prim(int n) {
for(int i=0;i<=n;i++) {
mincost[i]=INF;
}
mincost[0]=0;
while(true) {
int u=-1;
for(int i=0;i<=n;i++) {
if(!vis[i]&&(u==-1||mincost[i]<mincost[u])) {
u=i;
}
}
if(u==-1) {
break;
}
vis[u]=true;
ans+=mincost[u];
for(int i=0;i<=n;i++) {
if(!vis[i]&&mincost[i]>e[u][i]) {
mincost[i]=e[u][i];
root[i]=u;
}
}
}
}
public static void main(String args[]) {
InputReader sc=new InputReader(System.in);
PrintWriter out=new PrintWriter(System.out);
int n=sc.nextInt();
int cd[][]=new int[maxn][4];
for(int i=1;i<=n;i++) {
cd[i][0]=sc.nextInt();
cd[i][1]=sc.nextInt();
}
for(int i=1;i<=n;i++) {
cd[i][2]=sc.nextInt();
}
for(int i=1;i<=n;i++) {
cd[i][3]=sc.nextInt();
}
for(int i=1;i<=n;i++) {
for(int j=i;j<=n;j++) {
e[i][j]=e[j][i]=(long)(Math.abs((cd[i][0]-cd[j][0]))+Math.abs((cd[i][1]-cd[j][1])))*(cd[i][3]+cd[j][3]);
}
}
for(int i=1;i<=n;i++) {
e[i][0]=e[0][i]=cd[i][2];
}
prim(n);
int jizhan[]=new int[maxn];
int lianxian[][]=new int[maxn][2];
int index1=0;
int index2=0;
for(int i=1;i<=n;i++) {
if(root[i]==0) {
jizhan[index1++]=i;
}else {
lianxian[index2][0]=i;
lianxian[index2++][1]=root[i];
}
}
out.println(ans);
out.println(index1);
for(int i=0;i<index1;i++) {
out.print(jizhan[i]+" ");
}
out.println();
out.println(index2);
for(int i=0;i<index2;i++) {
out.println(lianxian[i][0]+" "+lianxian[i][1]);
}
out.flush();
out.close();
}
}