1151 - Buy or Build(二进制枚举子集 + 并查集)

这题LRJ书上翻译的有问题,书上说两点之间的cost是两点的欧几里得距离,而题目要求两点的距离是两点欧几里得距离的平方。

其余就没什么好说的了,裸的并查集,需要注意的就是二进制枚举子集的问题。

二进制枚举子集:

    for(int i = 0 ; i < (1 << s) ; i++){   /*s是集合元素的个数*/
        for(int j = 0 ; j < s ; j++){
            if(!(s >> j) & 1)
                continue;
            else{

            }

        }
    }
14054883 1151 Buy or Build Accepted C++ 0.118 2014-08-17 10:37:07

注释都加了,就不多解释了:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<list>
#include<cmath>
#include<string>
#include<sstream>
#include<ctime>
using namespace std;
#define _PI acos(-1.0)
#define esp 1e-9
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> pill;
/*===========================================
===============KinderRiven===================
===========================================*/
#define MAXD 1100  /*最多1000个点*/
vector<int>array;
int n , m , line_size;
int fa[MAXD];
struct Line{
    int l;
    int r;
    int cost;
    friend bool operator < (Line p,Line q){
        if(p.cost < q.cost)
            return true;
        else
            return false;
    }
}line[MAXD * MAXD];
struct Point{
    int x;
    int y;
}p[MAXD];
struct Pow{
    int cost;
    int size;
    int arr[MAXD];
}q[15];
int _dist(Point p,Point q){
    int x = p.x - q.x;
    int y = p.y - q.y;
    int ans =  x * x +  y * y;
    return ans;
}
int find_father(int u){
    return fa[u] == u ? u : fa[u] = find_father(fa[u]);
}
void init(){
    for(int i = 0 ; i <= n ; i++) fa[i] = i;
    return ;
}
LL solve(int u){
    int now = 0;  /*加的边数*/
    int cost = 0;
    init();
    for(int i = 0 ; i < m ; i ++){
        if(!((u >> i) & 1)) continue;   /*二级制枚举子集*/
        cost += q[i].cost;              /*套餐的花费*/
        for(int j = 1 ; j < q[i].size ; j++){
            int t1 = find_father(q[i].arr[j]);
            int t2 = find_father(q[i].arr[j - 1]);
            if(t1 != t2){
                fa[t1] = t2;
                now ++;
            }
        }
    }
    for(int i = 0 ; now < n - 1; i++){
         int   c = array[i];              /*找到边的编号*/
         int _t1 = line[c].l;             /*找到边的两个端点*/
         int _t2 = line[c].r;
         int t1 = find_father(_t1);
         int t2 = find_father(_t2);
         if(t1 != t2){
            cost += line[c].cost;
            fa[t1] = t2;
            now++;
         }
    }
    return cost;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        array.clear();
        LL _ans = 0;
        scanf("%d%d",&n,&m);
        init();
        for(int i = 0 ; i < m ; i++){
            scanf("%d%d",&q[i].size,&q[i].cost);
            for(int j = 0 ; j < q[i].size ; j ++)
                scanf("%d",&q[i].arr[j]);
        }
        line_size = 0;
        /*求出所有的边*/
        for(int i = 1 ; i <= n ; i++){
            scanf("%d%d",&p[i].x,&p[i].y);  /*输出坐标*/
            for(int j = 1 ; j < i ; j++){   /*求出1 ~ i - 1个点与i的距离*/
                line[line_size].l = j;
                line[line_size].r = i;
                line[line_size].cost = _dist(p[i],p[j]); /*求出2个点如果连接需要的花费*/
                line_size ++;
            }
        }
        /*找最短路,并记录最短路的每条边*/
        /*先排序*/
        sort(line,line + line_size);
        for(int i = 0 ,j = 0 ; j < n - 1; i ++){  /*当连线为n - 1的时候说明n个点都连通了*/
             int l = line[i].l , r = line[i].r;   /*找一个线段的2个端点*/
             int fa_l = find_father(l);           /*利用并查集找出其连通分量*/
             int fa_r = find_father(r);
             if(fa_l != fa_r){                    /*如果不在同一个连通分量*/
                fa[fa_l] = fa_r;                  /*合并!*/
                j ++;                             /*多加一条边*/
                _ans += line[i].cost;             /*费用*/
                array.push_back(i);               /*添加最小边*/
             }
        }
        /*二级制枚举子集!*/
        for(int i = 1 ; i < (1 << m) ; i++){
            LL ans = solve(i);
            _ans = min(ans,_ans);
        }
        printf("%lld\n",_ans);
        if(T > 0)
        printf("\n");
    }
    return 0;
}

1151 - Buy or Build(二进制枚举子集 + 并查集),布布扣,bubuko.com

时间: 2024-10-07 02:17:12

1151 - Buy or Build(二进制枚举子集 + 并查集)的相关文章

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

1151 - Buy or Build (最小生成树)

一道很好的最小生成树题目 . 看似非常复杂,其实仔细分析一下算法的复杂度就会发现,如果加入了lrj说的优化,其实复杂度不高 . 就像紫书中说的, 除去购买套餐中的点,剩下的最小边仍然在原始的最小生成树中 .  所以我们用二进制枚举子集的方法枚举所有购买套餐的组合,然后将套餐中的点加入并查集中,再用原始最小生成树中的边补全当前生成树 . 二进制枚举子集的复杂度是2^8 . 补全生成树的复杂度是O(n) . 所以最后复杂度为O(n*(2^8)) ,约等于10^6 ,可以接受. 有一个地方我不知道是不

【UVA】11464-Even Parity(二进制枚举子集)

枚举第一行的所有可能情况,之后根据上面行计算下面行(判断是否冲突),获得最终结果. 14058243 11464 Even Parity Accepted C++ 0.275 2014-08-18 05:14:15 #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<vector> #include<stack> #inc

UVa 1151 Buy or Build (最小生成树+二进制法暴力求解)

题意:给定n个点,你的任务是让它们都连通.你可以新建一些边,费用等于两点距离的平方(当然越小越好),另外还有几种“套餐”,可以购买,你购买的话,那么有些边就可以连接起来, 每个“套餐”,也是要花费的,让你求出最少花费. 析:首先想到的是把所有情况都考虑算一下,然后找出最少的,先算没有“套餐”的,然后算有的,用二进制枚举的话,总时间复杂度为O(2qn2+n2logn),这个时间复杂度太大了吧,肯定会超时, 那么我们就可以优化一下,首先先算出来最小生成树,并且把每条边都保存下来,那么加了“套餐”之后

hdu 1557 权利指数 ( 二进制枚举子集) 解题心得

原题: Description 在选举问题中,总共有n个小团体,每个小团体拥有一定数量的选票数.如果其中m个小团体的票数和超过总票数的一半,则此组合为“获胜联盟”.n个团体可形成若干个获胜联盟.一个小团体要成为一个“关键加入者”的条件是:在其所在的获胜联盟中,如果缺少了这个小团体的加入,则此联盟不能成为获胜联盟.一个小团体的权利指数是指:一个小团体在所有获胜联盟中成为“关键加入者”的次数.请你计算每个小团体的权利指数. Input 输入数据的第一行为一个正整数T,表示有T组测试数据.每一组测试数

uva 1151 - Buy or Build poj 2784 Buy or Build(最小生成树)

也是简单的最小生成树算法 不过添加了一些新的东西,需要对最小生成树算法 以及其中的 并查集的使用 有一些比较深入的理解. 处理问题的方法也有些复杂 #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; const int maxn = 1005; struct point { int x; int y; }pp[maxn]; s

做实验 解题报告(二进制枚举子集)

题目描述 有一天,你实验室的老板给你布置的这样一个实验. 首先他拿出了两个长度为 n 的数列 a 和 b,其中每个 a i 以二进制表示一个集 合.例如数字 5 = (101) [2] 表示集合 {1, 3}.第 i 次实验会准备一个小盒子,里面装 着集合 a i 所有非空子集的纸条.老板要求你从中摸出一张纸条,如果满足你摸出的 纸条是 a i 的子集而不是 a i?b i ,a i?b i +1 ,...,a i?1 任意一个的子集,那么你就要被阿掉; 反之,你就逃过一劫. 令你和老板都没有想

UVA 1151 Buy or Build

先求出原图的最小生成树,然后枚举买哪些套餐,把一个套餐内的点相当与边权为0,直接用并查集缩点.正确性是基于一个贪心, 在Kruskal中,对于没有进入最小生成树的边,排序在它前面的边不会减少. 边比较多,用prim求最小生成树,效果比Kruskal好,枚举套餐的时候在用Kruskal. #include<bits/stdc++.h> using namespace std; const int maxn = 1005; int n,q; int C[9]; vector<int>

(二进制枚举子集)买玩具

问题描述 蒜厂幼儿园有 n 个小朋友,每个小朋友都有自己想玩的玩具.身为幼儿园园长的你决定给幼儿园买一批玩具,由于经费有限,你只能买 m 个玩具.已知玩具商店一共卖 k 种玩具,编号为 1,2,3,…k,你让每个小朋友把想玩的玩具编号都写在了纸上.你希望满足尽可能多的小朋友的需求,请计算出最多同时能满足多少个小朋友的玩具需求. 输入格式 第一行,输入三个整数 n,m,k(1≤n≤100,1≤m≤k≤15),中间用空格分开. 接下来 n 行,第 i+1(0≤i< n) 行的第一个数字 ai代表第