Buy or Build(最小生成树+状压枚举)

题意:

n个城市,告诉每个城市的坐标,还有q个联通块,现在要把这n个城市连起来,可以购买联通块(每个有一定的费用),或者新建一条边(费用为点之间的距离的平方),问最小费用是多少。

思路:

首先可以想到朴素的做法,二进制枚举每个连通块选还是不选,判断该状态下图是否已经联通,如果未联通的话再从朴素的边里选择,时间复杂度为\(O(2^{q}*n^{2}+n^{2}*log_{n})\)
考虑怎么优化,要理解kruskal的算法本质,就是按照边权排序后先选择小的边,那么我们用连通块去替换已选的边时,只会替换那些原本就存在最小生成树中的边,这样才能保证最优解。
也就是说,我们先对原图做一遍kruskal,并且记录出所有要选择的边的集合记作\(edge1\),然后我们二进制枚举连通块,判断该状态下图是否联通,如果未联通的话就从\(edge1\)里加边,这样就完成了很大的剪枝。时间复杂度为\(O(2^{q}*(n-1)+n^{2}*log_{n})\)

代码:


const int maxn=1e6+7;

struct node{
	ll u,v,w;
};
node edge1[500500],edge[500500];
struct node1{
	ll m,w;
	vector<int>v;
}a[10];
int root[1100],n,q;
PLL pos[1100];

bool cmp(node a,node b){
	return a.w<b.w;
}

int Find(int x){
	if(x!=root[x]) root[x]=Find(root[x]);
	return root[x];
}

void init(int n){
	for(int i=1;i<=n;i++) root[i]=i;
}

ll dis(PLL a,PLL b){
	return (a.first-b.first)*(a.first-b.first)+(a.second-b.second)*(a.second-b.second);
}

void Union(vector<int>v,ll &tot){
	for(int i=0;i<v.size();i++){
		for(int j=i+1;j<v.size();j++){
			int fu=Find(v[i]),fv=Find(v[j]);
			if(fu!=fv) root[fu]=fv,tot++;
		}
	}
}

void solve(){
    n=read,q=read;
    for(int i=0;i<q;i++) a[i].v.clear();
    for(int i=0;i<q;i++){
    	a[i].m=read,a[i].w=read;
    	for(int j=1;j<=a[i].m;j++){
    		int x=read;
    		a[i].v.push_back(x);
    	}
    }
    for(int i=1;i<=n;i++) pos[i].first=read,pos[i].second=read;
    ll idx=0;
    for(int i=1;i<=n;i++){
    	for(int j=i+1;j<=n;j++){
    		edge[++idx]={i,j,dis(pos[i],pos[j])};
    	}
    }
    sort(edge+1,edge+1+idx,cmp);
    init(n);
    ll cnt=0,ans=0;
    for(int i=1;i<=idx;i++){
    	int u=edge[i].u,v=edge[i].v;
    	int fu=Find(u),fv=Find(v);
    	if(fu!=fv){
    		cnt++;
    		root[fu]=fv;
    		edge1[cnt]={u,v,edge[i].w};
    		ans+=edge[i].w;
    	}
    	if(cnt==n-1) break;
    }
    for(int i=0;i<(1<<q);i++){
    	ll res=0,tot=0;
    	init(n);
    	for(int j=0;j<q;j++)
    		if((i>>j)&1){
    			res+=a[j].w;
    			Union(a[j].v,tot);
    		}
    	if(tot==n-1){
    		ans=min(ans,res);
    	}
    	else{
    		for(int j=1;j<=n-1;j++){
    			int u=edge1[j].u,tv=edge1[j].v,w=edge1[j].w;
    			int fu=Find(u),fv=Find(tv);
    			if(fu!=fv){
    				tot++;
    				root[fu]=fv;
    				res+=w;
    			}
    			if(tot==n-1) break;
    		}
    		ans=min(ans,res);
    	}
    }
    printf("%lld\n",ans);
}

int main() {
	int T=read;
	while(T--){
		solve();
		if(T) puts("");
	}
	return 0;
}

posted @ 2021-05-23 21:25  OvO1  阅读(51)  评论(0编辑  收藏  举报