自己用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;
}