Buy or Build UVA - 1151 (最小生成樹+列舉集合)
阿新 • • 發佈:2018-12-26
題意:現在在一個圖上有n的點,每個點給出相應的座標,然後兩個點之間的距離就是歐幾里得距離,之後還有幾個套餐,套餐具有相應的價錢,套餐會把套餐內所給的點聯通,最後問最小花費是多少呢?
題解:如果首先列舉集合的話呢?那麼每次列舉完之後都會再進行一次求解最小生成樹的過程,這樣複雜度很高啊,首先列舉集合就有o(n^2)的複雜度,然後求解最小生成樹又是o(logn)的排序加o(n)的貪心,複雜度要爆炸啊,所以可不可以只求一次最小生成樹呢?如果我們記下第一次不要套餐時的最小生成樹的邊都是哪些邊,那麼是不是就可以直接列舉套餐,然後再從選出最小生成樹的邊中選出那些需要的邊,這種思想肯定是正確的,證明也是非常簡單,如果此時列舉套餐時,將部分點聯通了,那麼其他點必然要聯通,怎麼才能使得花費最小呢?肯定還是從小到打排序,然後還是看是否已經聯通,聯通了跳過即可,如果沒有聯通那麼就得加上此時這條邊,直到所有的點聯通即可。
附上程式碼:
#include<bits/stdc++.h> using namespace std; const int maxn=1e3+10; const int maxq=8; int n; int x[maxn],y[maxn],cost[maxq]; vector<int>subn[maxq]; int f[maxn]; int find(int x) { if(f[x]==x){ return x; }else{ return f[x]=find(f[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; } }; 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=find(e[i].u),v=find(e[i].v); int d=e[i].d; if(u!=v){ f[v]=u; 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++){ f[i]=i; } sort(e.begin(),e.end()); int ans=mst(n,e,need); for(int mask=0;mask<(1<<q);mask++){ for(int i=0;i<n;i++){ f[i]=i; } int cnt=n,c=0; 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=find(subn[i][0]),v=find(subn[i][j]); if(u!=v){ f[u]=v; cnt--; } } } } vector<Edge>dummy; ans=min(ans,c+mst(cnt,need,dummy)); } printf("%d\n",ans); if(T){ printf("\n"); } } return 0; }