UVA 1151 Buy or Build 最小生成樹+二進位制選取子集
阿新 • • 發佈:2019-02-02
題意:給你n個點,你的任務是讓這n個點連通。為此,你有兩種方法,1、在某兩點之間建邊,費用為兩點之間歐幾里得距離的平方。2、購買一些套餐,當買了某個套餐後,套餐中的這些點將變得相互連通,問完成任務的最小費用是多少。
思路:先按全都不選套餐,求出此時的最小生成樹,記錄最小生成樹的邊,然後用套餐中的邊來替換這些邊。因為第一次求的那些邊,肯定都是最優的,那些邊恰好構成一棵最小生成樹,不會再有比它小的了,購買套餐之後,結果就是,不僅以前求出的最優邊還在裡面,還會加入一些更優的邊(權值為0)
#include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> using namespace std; int a[10][1010],t[10],cost[10]; int f[1010]; struct p { int x; int y; int z; } e[500100],vis[1010]; bool cmp(p x,p y) { return x.z<y.z; } int getf(int v) { if(f[v]==v) return v; f[v]=getf(f[v]); return f[v]; } int merge(int u,int v) { int t1=getf(u); int t2=getf(v); if(t1!=t2) { f[t1]=t2; return 1; } return 0; } int main() { int T; int n,m; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0; i<m; i++) { scanf("%d%d",&t[i],&cost[i]); for(int j=0; j<t[i]; j++) scanf("%d",&a[i][j]); } int s1[1010],s2[1010]; for(int i=1; i<=n; i++) scanf("%d%d",&s1[i],&s2[i]); int sum=0; for(int i=1; i<=n; i++) { for(int j=i+1; j<=n; j++) { e[sum].x=i; e[sum].y=j; e[sum++].z=(s1[i]-s1[j])*(s1[i]-s1[j])+(s2[i]-s2[j])*(s2[i]-s2[j]);//兩條邊之間的花費 } } sort(e,e+sum,cmp); int k=0,cnt=0; for(int i=0; i<=n; i++) f[i]=i; int num=0; for(int i=0; i<sum; i++) { if(merge(e[i].x,e[i].y)) //不使用套餐找出最小生成樹 { k+=e[i].z; cnt++; vis[num].x=e[i].x; vis[num].y=e[i].y; vis[num++].z=e[i].z;//記錄使用了呢些邊,使用套餐時在已經記錄過的這裡面選邊,避免超時 } if(cnt==n-1) break; } for(int i=0; i<(1<<m); i++) { int k1=0,cnt=0; for(int j=0; j<=n; j++) f[j]=j; for(int j=0; j<m; j++) { if(i&(1<<j)) //二進位制選子集,考慮用或不用該套餐的每種情況 { int u=a[j][0]; for(int f=1; f<t[j]; f++) { int v=a[j][f]; if(merge(u,v)) cnt++; } k1+=cost[j]; } } for(int j=0;j<num;j++) { if(merge(vis[j].x,vis[j].y)) { k1+=vis[j].z; cnt++; } if(cnt==n-1) break; } k=min(k,k1); } printf("%d\n",k); if(T) printf("\n"); } return 0; }