zoukankan      html  css  js  c++  java
  • UVa1151

      这道题是一道最小生成树问题的题,与最原始的最小生成树不同的是,问题中添加了套餐,不过我们发现套餐的数量很小(<=8),所以我们可以枚举选择那些套餐,然后再次基础上进行最小生成树(Kruskal),下面是代码,我们需要注意的是由于边的数量很多,所以我们已经连n-1条边时我们就不用考虑后面了。

      代码:

    // UVa 1151
    #include <cstdio>
    #include <algorithm>
    #include <vector>
    using namespace std;
    
    const int maxp = 10;
    const int maxn = 1000 + 5;
    const int maxm = 500000 + 5; 
    const int INF = 100000000;
    
    int n, p, best;
    int x[maxn], y[maxn], fa[maxn], cnt;  
    
    struct Item { 
      vector<int> nodes; 
      int val, size;
    } item[maxp];
    
    struct Edge {
      int u, v, val;
      Edge(int u=0, int v=0, int val=0) : u(u), v(v), val(val) {}
        bool operator < (const Edge& rhs) const {
            return val < rhs.val; 
        }
    } edge[maxm]; 
    
    int dist(int a, int b) {
      return (x[a]-x[b]) * (x[a]-x[b]) + (y[a]-y[b]) * (y[a]-y[b]);  
    }
    
    void init() { for (int i = 1; i <= n; ++i) fa[i] = i; }
    
    int find(int x) { return x == fa[x] ? x : x = find(fa[x]); }
    
    int Kruskal(int set) { 
      init(); 
        int ans = 0, u, count = 0; 
      for (int i = 0; i < p; ++i) if (set&(1<<i)) {
          ans += item[i].val; 
            u = find(item[i].nodes[0]);     
            for (int j = 1; j < item[i].size; ++j) { int v = find(item[i].nodes[j]); if (v != u) fa[v] = u; }
      } 
      if (ans > best) return best; 
      
      for (int i = 0; i < cnt; ++i) { 
        int x = find(edge[i].u), y = find(edge[i].v), val = edge[i].val;  
        if (x != y) { ans += val; fa[x] = y; if (++count == n-1) break; }
        if (ans > best) return best;
      }
      return ans; 
    }
    
    int main() { 
        int T, kase;  
        scanf("%d", &T); 
        for (int kase = 0; kase < T; ++kase) {
            if (kase) printf("
    "); 
            scanf("%d%d", &n, &p); 
            for (int i = 0; i < p; ++i) {
              scanf("%d%d", &item[i].size, &item[i].val);
              item[i].nodes.clear(); 
              for (int j = 0; j < item[i].size; ++j) {
                  int k; 
                    scanf("%d", &k); 
                    item[i].nodes.push_back(k);      
              }
            }
            for (int i = 1; i <= n; ++i) scanf("%d%d", &x[i], &y[i]);
            
            cnt = 0; 
            for (int i = 1; i <= n; ++i) 
              for (int j = i+1; j <= n; ++j) 
                edge[cnt++] = Edge(i,j,dist(i,j));
            sort(edge, edge+cnt);  
            best = INF; 
            for (int i = 0; i < (1<<p); ++i) best = min(best, Kruskal(i));
            printf("%d
    ", best);      
        }
      return 0;
    }

      当已经连通的边为n-1时,我们就能够退出,然而时间复杂度是O(2^q*n^2+n^2logn),比较容易超时,然而我提交后竟然过了,所以数据出的很"和谐",那么如果出题人想要难为我们,他就可以尽量不满足n-1这个条件,就是让n-1个点很近,然而最后一个点非常的远,这样我们退出就需要等到最后了。

      所以我们需要优化算法,我们发现,对于选择边(选完套餐时候),我们也是尽量选择小的边,而无关紧要的边我们能不能提前排除呢?

    我们首先需要忽略套餐的条件,进行一次“裸”的Kruskal,然后将连通图的边确定下来,后面我们枚举套餐时仅仅考虑确定下来的边,这是因为我们选择一定的套餐时,套餐包含的边被视为“0”,于是相当于进行一次新的Kruskal,这样后面的边仍然不会被选中。

      下面是代码:

    // UVa 1151
    #include <cstdio> 
    #include <cstring>
    #include <algorithm>
    #include <vector> 
    #include <cmath>
    using namespace std; 
    
    const int maxn = 1000 + 5; 
    const int maxp = 8;
    
    int n, p; 
    int x[maxn], y[maxn], cost[maxp];
    vector<int> subn[maxp];
    
    int pa[maxn]; 
    int findset(int x) { return x == pa[x] ? x : pa[x] = findset(pa[x]); }
    
    struct Edge {
      int u, v, d; 
        Edge(int u=0, int v=0, int d=0) : u(u), v(v), d(d) {}
        bool operator < (const Edge& rhs) const {
            return d < rhs.d; 
        }
    }; 
    
    int dist(int a, int b) { return (x[a]-x[b])*(x[a]-x[b]) + (y[a]-y[b])*(y[a]-y[b]); } 
    
    int MST(int cnt, const vector<Edge>& a, vector<Edge>& b) {
      if (cnt == 1) return 0; 
      b.clear();
        int ans = 0, m = a.size();
        for (int i = 0; i < m; ++i) {
            int u = findset(a[i].u), v = findset(a[i].v), d = a[i].d;
            if (u != v) { ans += d; pa[u] = v; b.push_back(a[i]); if (--cnt == 1) break; }
        }
        return ans;
    }
    
    int main() { 
      int T;
      scanf("%d", &T); 
      while (T--) {
          scanf("%d%d", &n, &p); 
            for (int i = 0; i < p; ++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) 
                e.push_back(Edge(i, j, dist(i,j))); 
                
            for (int i = 0; i < n; ++i) pa[i] = i; 
            sort(e.begin(), e.end()); 
            
            int ans = MST(n, e, need); 
            for (int i = 0; i < (1<<p); ++i) {
                int c = 0, cnt = n; 
                for (int j = 0; j < n; ++j) pa[j] = j; 
              for (int j = 0; j < p; ++j) if (i&(1<<j)) {
                  c += cost[j]; 
                    for (int k = 1; k < subn[j].size(); ++k) {
                        int u = findset(subn[j][0]), v = findset(subn[j][k]); 
                        if (u != v) { pa[u] = v; --cnt; }     
                    }
              }
              vector<Edge> dummy; 
              ans = min(ans, c + MST(cnt, need, dummy)); 
            }
            printf("%d
    ", ans);
            if (T) printf("
    ");  
        }
      return 0;
    }
  • 相关阅读:
    无重叠区间
    ARC109E 1D Reversi Builder
    IOI2021集训队作业129CF Longest Rivers
    IOI2021集训队作业281CA Balanced Diet
    IOI2021集训队作业227CB Branch Assignment
    IOI2021集训队作业277BK Tours
    IOI2021集训队作业125AA Baggage
    6894. 【2020.11.25提高组模拟】小 S 埋地雷 (loj6611. 摧毁时间线)
    ARC108 题解&总结
    HTML教程
  • 原文地址:https://www.cnblogs.com/yifeiWa/p/11551906.html
Copyright © 2011-2022 走看看