第十一章例题 uva 1151 C- Buy or Build

自己用dfs实现枚举 + 对刘汝佳大神用二进制枚举的解读 

/*SE:wn------王宁*/
/*Buy or Build 输入输出解读
输入:
城市编号从1-n 
1.告诉你有几个case

2.1 n of cities in the country (1 ≤ n ≤ 1000)  2.2 followed by the number q of existing subnetworks (0 ≤ q ≤ 8).

3.(接下来q行) 
这个网络中的城市数量 + 这个网络买下来的花费 (not greater than 2 × 1e6)+ 网络中城市们的编号

4.(接下来n行)
城市的x y坐标  (ranging from 0 to 3000) c

输出:要有空行 
*/
/*1.运行过程出现的bug原因
当我以为建立网络的花费是距离而不是距离平方的时候
我用%d输出,结果虽然result已经是正确答案(样例中是12.多)
但是这样就输出了负数——检查了一遍才发现
2.重复运行的原因 ! 重复运行是必须的,对于我这种写法来说,
如果一个套餐都没有, v0是不是永远不可能==-1? 
之前的想法(dfs边界判断条件应该是
 if(v0==qn-1),因为是从0开始计数的,而choose在开头
 就已经独立于if-else语句初始化好了)对重复的解读是对的,
 但是没有考虑到特殊情况,结果为了优化又搞出了bug 
 所以还是 if(v0==qn)*/
#include<bits/stdc++.h>
using namespace std; 
struct edge{
  int u,v,d;
  bool operator < (const edge& dhs) const{
    return d < dhs.d;
  }
};
struct point{
  int x,y;
};
const int maxn=1000+5;

vector<edge> database;//经过无套餐参与的最小生成树边集 
vector<edge> total;//经过套餐可能的参与的需要探索的总边集 
vector<edge> tc[10];//每个套餐含有的边集 
vector<edge> v;//经过第一次欧几里得距离的计算处理后得到的原始边集 

vector<point> p;//存储城市的位置的点集 
vector<int> a;//接收套餐中城市编号的数组 
struct edge t;//接受边的临时变量 
struct point pt;//接受城市位置的临时变量 
int first=1;
int fa[maxn];
int nc,qn;//几个城市,几个套餐
int choose[10];//套餐有没有被选择的状态数组
int result=2000000000,ans;//ans是每次枚举套餐得到的局部最小值,result保存全局最小值
int cost[10];//每个套餐花的钱
int find(int x){
  return x==fa[x]?x:fa[x]=find(fa[x]);
}
/*枚举使用了dfs——因为不知道有几个套餐
——知道了用for来写也很丑
——对,我懒得写
——好了,你?怎么还在看
——我不会写行了嘛555……*/
void dfs(int v0, int state){//state表示该套餐有没有被选择,0=没有,1=选择了
  choose[v0]=state;
  if(v0==qn){
    total=database;
    ans=0;
    /*来得到需要处理的边集,注意
    1.vector变量之间可以直接赋值
    2.它的insert用法*/
    for(int i=0;i<qn;i++)
      if(choose[i]==1){ ans=ans+cost[i]; total.insert(total.end(), tc[i].begin(), tc[i].end() ); } 
    //开始在总的边集里面找最小生成树
  sort(total.begin(),total.end());//printf("我到了试验田第一部分啦\n");
  /*for(int i=0;i<total.size();i++){
      cout<<total[i].u<<" "<<total[i].v<<" "<<total[i].d<<endl;
  }*/
  int cnt=0;
  for(int i=1;i<=nc;i++) fa[i]=i;
    for(int i=0;i<total.size();i++){
      int x=find(total[i].u),y=find(total[i].v);
      if(x!=y) {
        fa[x]=y;
        ans=ans+total[i].d;
        if(++cnt==nc-1) break;
      }
    }
   // printf("经过试验,此次的花费ans=%d\n",ans);
    result=min(result,ans);
   // printf("经过试验,此次的result=%d\n",result);
  }
  else{
    dfs(v0+1,0);
    dfs(v0+1,1);
  }
}

int main()
{
  int runs,run,i,j,k,m,c,tmp,cnt;
  cin>>runs;
  for(run=1;run<=runs;run++){
    cin>>nc>>qn;
    /*处理套餐*/
    for(i=0;i<qn;i++)// √ 
    {
      cin>>m>>c; cost[i]=c; a.clear(); tc[i].clear(); 
      for(j=0;j<m;j++) {
        scanf("%d",&tmp);
        a.push_back(tmp);
      }
      for(j=0;j<m;j++) 
      for(k=j+1;k<m;k++){
        t.u=a[j]; t.v=a[k]; t.d=0;
        tc[i].push_back(t);
      }
    }
    /*处理套餐*/

    /*处理城市*/
    v.clear();
    p.clear();
    for(i=1;i<=nc;i++)
    {
      scanf("%d%d",&pt.x,&pt.y);
      p.push_back(pt);
    }
    for(i=0;i<nc;i++)
      for(j=i+1;j<nc;j++)
      {
        t.u=i+1; t.v=j+1; t.d=(p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y);
        v.push_back(t);
      }
    sort(v.begin(),v.end());
    /*for(i=0;i<v.size();i++){
      cout<<v[i].u<<" "<<v[i].v<<" "<<v[i].d<<endl;
  } */
    /*处理城市*/

    /*找出最小生成树的n-1条边 √*/
    database.clear(); cnt=0;
    for(i=1;i<=nc;i++) fa[i]=i;
    for(i=0;i<v.size();i++){
      int x=find(v[i].u),y=find(v[i].v);
      if(x!=y) {
        fa[x]=y;
        database.push_back(v[i]);
        if(++cnt==nc-1) break;
      }
    }
    /*total=database;
    for(i=0;i<total.size();i++)
      cout<<total[i].u<<" "<<total[i].v<<" "<<total[i].d<<endl;
    total.insert(total.end(), tc[1].begin(), tc[1].end());
    for(i=0;i<total.size();i++)
      cout<<total[i].u<<" "<<total[i].v<<" "<<total[i].d<<endl;*/
    //for(i=1;i<=nc;i++) cout<<fa[i]<<" "; 
    /*for(i=0;i<database.size();i++)
      cout<<database[i].u<<" "<<database[i].v<<" "<<database[i].d<<endl;*/
    /*找出最小生成树的n-1条边*/

    /*目前状况是:database装载了不考虑套餐时的最小生成树所需要的边
    tc[0~qn-1]装载了每个套餐中含有的边,这些边的权重是0,
    cost记载了每个套餐需要的钱*/
    /*然后呢?我要写一个八层循环?
    如果选择这个套餐,就把这个套餐里面的边包含到总的边集去,然后加上该套餐的花费,
    那么八层的for应该放在什么位置?可是我不知道有几个循环啊
    那就用递归吧?*/
    dfs(0,0);
    dfs(0,1);
    if(first) first=0;
    else cout<<endl;
    printf("%d\n",result);
    result=2000000000;
  }
  return 0;
}

———————帅气不失风度的分割线————————


// UVa1151 Buy or Build
// Rujia Liu processed by SE:wn------王宁
#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) {
  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());

/*对刘汝佳大神用二进制位运算来实现使用for循环的枚举*/
	int ans = MST(n, e, need);//没有购买套餐的状态
    for(int mask = 0; mask < (1<<q); mask++) {
    /*比如有两个套餐,q=2
    1<<q=4,那么就得到了以下01串:
    000 001 010 011 !100(因为<4)
    对应
    0   1   2   3
    最后两位是不是能刚好满足枚举?*/      
    // union cities in the same sub-network
      for(int i = 0; i < n; i++) pa[i] = i;
      int cnt = n, c = 0;
  		//只要不是0,二进制位与都能和mask
  //然后0,1会在每一轮mask循环里面和mask位与
  //起作用的(购买套餐)是不是有01 10 11?
  //01 和 1<<0=1=01 位与 ,是不是代表着第一个套餐能被选中?
  //同样的,01和1<<1=2=10,额,这个位与就变成0了,
  //(第二个mask)10 和 01,第一个套餐没有选中
  /*10 和 10 选中
  第三个套餐,显然都会选中
  如果q=3,mask 从0000 0001 0010 0011 0100 0101 0110 0111
  等一下,那些有什么特点 1<<i
  1 2 4 
  0001 0010 0100 
  配合所有的状态1<<q,
  0000 0001 0010 0011 0100 0101 0110 0111
  所以,这些状态表明谁可以进来,谁不可以进来
  谁想要进来,必须提供自己本身的身份钥匙,只要
  自己位置上的锁是有洞的(1),就能够通过验证
  */
      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--; }
        }
      }
      //dummy有什么用呢 ? 一开始把need放在dummy的位置上能获得没有套餐参与的最小生成树的边,后面就没有用了吧?
	  //嗯,没用了,每次进去都会被清空掉,只有need被保留了下来,话说你使用之前都要重新定义一次,没有clear也一样 
      vector<Edge> dummy;
      ans = min(ans, c + MST(cnt, need, dummy));
    }
    printf("%d\n", ans);
    if(T) printf("\n");
  }
  return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值