UVA1151

//感觉刘汝佳老师的思维真的太厉害了orz
/*摘录书上的一段话: 只需一个小小的优化即可降低时间复杂度:先求一次原图(不购买任何套餐)的最小生
成树,得到n-1条边,然后每次枚举完套餐后只考虑套餐中的边和这n-1条边,则枚举套餐之
后再求最小生成树时,图上的边已经寥寥无几。
为什么可以这样呢?首先回顾一下,在Kruskal算法中,哪些边不会进入最小生成树。答
案是:两端已经属于同一个连通分量的边。买了套餐以后,相当于一些边的权变为0,而对
于不在套餐中的每条边e,排序在e之前的边一个都没少,反而可能多了一些权值为0的边,
所以在原图Kruskal时被“扔掉”的边,在后面的Kruskal中也一样会被扔掉。*/

// UVa1151 Buy or Build
// Rujia Liu
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn = 1000 + 10;
const int maxq = 8;
int n;
int x[maxn], y[maxn], cost[maxq];
vector<int> subn[maxq];

int pa[maxn];
int findset(int x) { return pa[x] != x ? pa[x] = findset(pa[x]) : x; } 

struct Edge {
  int u, v, d;
  Edge(int u, int v, int d):u(u),v(v),d(d) {}
  bool operator < (const Edge& rhs) const {
    return d < rhs.d;
  }
};

// initialize pa and sort e before calling this method
// cnt is the current number of components
int MST(int cnt, const vector<Edge>& e, vector<Edge>& used) {  //找出原图跑一边kruskal之后用过的边
  if(cnt == 1) return 0;
  int m = e.size();
  int ans = 0;
  used.clear();
  for(int i = 0; i < m; i++) {
    int u = findset(e[i].u), v = findset(e[i].v);
    int d = e[i].d;
    if(u != v) {
      pa[u] = v;
      ans += d;
      used.push_back(e[i]);
      if(--cnt == 1) break;
    }
  }
  return ans;
}

int main() {
  int T, q;
  scanf("%d", &T);
  while(T--) {
    scanf("%d%d", &n, &q);
    for(int i = 0; i < q; i++) {
      int cnt;
      scanf("%d%d", &cnt, &cost[i]);
      subn[i].clear();
      while(cnt--) {
        int u;
        scanf("%d", &u);
        subn[i].push_back(u-1);
      }
    }
    for(int i = 0; i < n; i++) scanf("%d%d", &x[i], &y[i]);

    vector<Edge> e, need;
    for(int i = 0; i < n; i++)
      for(int j = i+1; j < n; j++) {
        int c = (x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]);
        e.push_back(Edge(i, j, c));
      }

    for(int i = 0; i < n; i++) pa[i] = i;
    sort(e.begin(), e.end());

    int ans = MST(n, e, need);
    for(int mask = 0; mask < (1<<q); mask++) {  //枚举套餐,二进制法
      // union cities in the same sub-network
      for(int i = 0; i < n; i++) pa[i] = i;
      int cnt = n, c = 0;
      for(int i = 0; i < q; i++) if(mask & (1<<i)) {
        c += cost[i];
        for(int j = 1; j < subn[i].size(); j++) {
          int u = findset(subn[i][j]), v = findset(subn[i][0]);
          if(u != v) { pa[u] = v; cnt--; }
        }
      }
      vector<Edge> dummy;
      ans = min(ans, c + MST(cnt, need, dummy));
    }
    printf("%d\n", ans);
    if(T) printf("\n");
  }
  return 0;
}
时间: 2024-08-04 16:39:51

UVA1151的相关文章

UVa1151 Buy or Build (最小生成树,枚举子集)

链接:http://bak.vjudge.net/problem/UVA-1151 分析:先在原图上跑一遍MST,得到n-1条边,然后其它的边完全可以抛弃掉,因为它们不会比这n-1条边更优,这样就可以把原图边的数量减少到n-1条,并且得到ans初值. 接下来就是通过枚举套餐子集,生成一个套餐费用c1并且得到若干联通分量,再在这些联通分量基础上跑MST得到最小花费c2,更新答案ans. 1 #include <cstdio> 2 #include <vector> 3 #includ

uva1151(最小生成树)

题目连接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3592 二进制枚举 1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<algorithm> 5 #define ll long long 6 us

UVa1151&amp;POJ2784--Buy or Build【kruskal+二进制枚举】

链接: UVa http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3592 POJ http://poj.org/problem?id=2784 题意:告诉你n个点的坐标,建立一颗最小生成树,不过有q个套餐,套餐是连通某些点,并有一定花费,求最小生成树. 思路:n个点,n最大为1000,则最多有1000*999/2条边,先不使用套餐求一遍最小生

【最小生成树+子集枚举】Uva1151 Buy or Build

Description 平面上有n个点(1<=N<=1000),你的任务是让所有n个点连通,为此,你可以新建一些边,费用等于两个端点的欧几里得距离的平方. 另外还有q(0<=q<=8)个套餐,可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相互连通,第i个套餐的花费为ci. 求最小花费. Solution 对于套餐可以用子集枚举处理,求最小生成树时只需考虑原图是最小生成树中的边. 正确性可以按Kruskal过程,以前被舍弃的边选了套餐后依然会被舍弃. Code 1 #in

UVa 1151 买还是建

https://vjudge.net/problem/UVA-1151 题意: 平面上有n个点,你的任务是让所有n个点连通.为此,你可以新建一些边,费用等于两个端点的距离平方和.另外还有q个套餐可以购买,如果你购买了第i个套餐,该套餐中的所有结点都变得相互连通,第i个套餐的花费为Ci. 思路: 这道题比较容易超时.可能需要用到并查集的路径压缩,我下面的代码就是用了路径压缩,不然要超时.也是看了别人的代码才知道还有这种省时间的做法. 先介绍一下路径压缩吧: 如果并查集像一字长蛇这样排列的话,寻找起

uva 1511 最小生成树

https://vjudge.net/problem/UVA-1151 题意,给出N个点以及二维坐标,可以在任意两点间建立通路,代价是两点欧几里得距离的平方,同时有q个套餐,套餐x有qx个点,代价是qw, 花费qw就能将这qx个点全部相联通,套餐可以任意选择几种或不选,求将所有的点联通所要的最小代价. 很容易想到一个暴力做法,遍历2^q种套餐方案,将这些点提前加入后再跑kruskal,这样的复杂度有些高了. 其实简单证明一下,我们可以先不买套餐跑一遍kruskal,之后保存下来所选的边. 遍历所

第11章 图论模型与算法

再谈树 无根树转有根数 #include<bits/stdc++.h> using namespace std; const int maxn = 100; vector<int> G[maxn]; int n; void read_tree(){ int u,v; scanf("%d",&n); for(int i=0;i<n-1;i++){ scanf("%d%d",&u,&v); G[u].push_bac

9.1总结前日(数学+图论)

后天就要开学了,这应该是这个暑假的最后一次总结了吧. 说实话,忙忙碌碌的一个暑假,学到东西了么?学到了.学了多少?还可以吧hhh. 想起来去年的这个时候,我还抱着紫书在那里看爆搜,啥也看不懂,现在呢,怎么说也懂得了一些吧. 路就是这样,你敢走,就有的走. 狭路相逢,勇者胜. UVA 1645 题意:给出一个数n,求n个结点的树有多少种结构满足每个结点的子结点数相同. 解法:n结点树,除去根结点,有n-1个结点,根结点的每棵子树需要完全相同, 所以根结点的子树个数k,它们满足(n-1)%k==0.