链接:
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3592
题意:
平面上有n个点(1≤n≤1000),你的任务是让所有n个点连通。
为此,你可以新建一些边,费用等于两个端点的欧几里德距离的平方。
另外还有q(0≤q≤8)个“套餐”可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相互连通。
第i个套餐的花费为Ci。求最小的花费。
分析:
最容易想到的算法是:先枚举购买哪些套餐,把套餐中包含的边的权值设为0,然后求最小生成树。
由于枚举量为O(2^q),给边排序的时间复杂度为O(n*nlogn),而排序之后每次Kruskal算法的时间复杂度为O(n*n),
因此总时间复杂度为O((2^q)*(n*n)+n*nlogn),对于题目的规模来说太大了。
只需一个小小的优化即可降低时间复杂度:先求一次原图(不购买任何套餐)的最小生成树,
得到n-1条边,然后每次枚举完套餐后只考虑套餐中的边和这n-1条边,
则枚举套餐之后再求最小生成树时,图上的边已经寥寥无几。
为什么可以这样呢?首先回顾一下,在Kruskal算法中,哪些边不会进入最小生成树。
答案是:两端已经属于同一个连通分量的边。买了套餐以后,相当于一些边的权变为0,
而对于不在套餐中的每条边e,排序在e之前的边一个都没少,反而可能多了一些权值为0的边,
所以在原图Kruskal时被“扔掉”的边,在后面的Kruskal中也一样会被扔掉。
代码:
1 import java.io.*; 2 import java.util.*; 3 import static java.lang.Math.*; 4 5 public class Main { 6 Scanner cin = new Scanner(new BufferedInputStream(System.in)); 7 final int UP = 1000 + 5; 8 int pre[] = new int[UP]; 9 int x[] = new int[UP], y[] = new int[UP], cost[] = new int[UP]; 10 11 class Edge implements Comparable<Edge> { 12 int f, b, v; 13 14 @Override 15 public int compareTo(Edge that) { 16 return v - that.v; 17 } 18 } 19 20 int seek(int v) { 21 return pre[v] == v ? v : (pre[v] = seek(pre[v])); 22 } 23 24 int MST(int n, ArrayList<Edge> e, ArrayList<Edge> res) { 25 if(n <= 1) return 0; 26 int m = e.size(), ans = 0; 27 for(int i = 0; i < m; i++) { 28 int pf = seek(e.get(i).f), pb = seek(e.get(i).b); 29 if(pf == pb) continue; 30 pre[pf] = pre[pb]; 31 ans += e.get(i).v; 32 if(res != null) res.add(e.get(i)); 33 if(--n == 1) break; 34 } 35 return ans; 36 } 37 38 void MAIN() { 39 int T; 40 T = cin.nextInt(); 41 while(T --> 0) { 42 @SuppressWarnings("unchecked") 43 ArrayList<Integer> subn[] = new ArrayList[8]; 44 for(int i = 0; i < 8; i++) subn[i] = new ArrayList<Integer>(); 45 int n = cin.nextInt(); 46 int q = cin.nextInt(); 47 for(int m, i = 0; i < q; i++) { 48 m = cin.nextInt(); 49 cost[i] = cin.nextInt(); 50 for(int t = 0; t < m; t++) subn[i].add(cin.nextInt()-1); 51 } 52 for(int i = 0; i < n; i++) { 53 x[i] = cin.nextInt(); 54 y[i] = cin.nextInt(); 55 } 56 57 ArrayList<Edge> edge = new ArrayList<Edge>(); 58 for(int i = 0; i < n; i++) { 59 for(int t = i+1; t < n; t++) { 60 Edge e = new Edge(); 61 e.f = i; e.b = t; 62 e.v = (x[i]-x[t])*(x[i]-x[t]) + (y[i]-y[t])*(y[i]-y[t]); 63 edge.add(e); 64 } 65 } 66 67 ArrayList<Edge> used = new ArrayList<Edge>(); 68 for(int i = 0; i < n; i++) pre[i] = i; 69 Collections.sort(edge); 70 int ans = MST(n, edge, used); 71 for(int s = 1; s < (1<<q); s++) { 72 for(int i = 0; i < n; i++) pre[i] = i; // 初始化并查集 73 int remain = n, c = 0; 74 for(int i = 0; i < q; i++) if((s&(1<<i)) > 0) { 75 c += cost[i]; 76 for(int t = 1; t < subn[i].size(); t++) { 77 int pf = seek(subn[i].get(0)), pb = seek(subn[i].get(t)); 78 if(pf == pb) continue; 79 pre[pf] = pb; 80 remain--; 81 } 82 } 83 ans = min(ans, c + MST(remain, used, null)); 84 } 85 System.out.println(ans); 86 if(T > 0) System.out.println(); 87 } 88 } 89 90 public static void main(String args[]) { new Main().MAIN(); } 91 }