UVA 1151 Buy or Build(最小生成树,Kruskal)
对于n(1<=n<=1000)个点,q(0<=q<=8)个套餐,如果直接建边并枚举套餐的话,每次Kruskal算法要处理的节点数就会高达O(n2)个,那么整个算法的时间复杂度就高达O(2q n2+n2logn2)。其中O(n2logn2)是排序的时间复杂度。
为了降低时间复杂度,可以先通过Kruskal算法在不选套餐的情况下生成最小生成树,那么剩下的边只有n-1个,那么后面只需要处理这n-1条边和套餐里的边,算法的效率有了大大的提高。时间复杂度为O(2q n+nlongn)。
有个可能的问题是,后续本来要选的边会不会通过预处理被忽略掉了?
答案是不会,Kruskal算法以边的权值作为排序依据,并且只选两个端点不属于一个连通分量的边。由于把套餐内边的权值设置为0,对于每一个不在套餐内的边,排在它前面的边只增不减。如果该边在预处理的时候被抹去,那么在后续中也会被抹去。
我一开始有个想法是,将一个套餐里的点设置为同一个代表元,这样Kruskal算法就会忽略以这些点为端点的边,但是问题是,一个点可能会出现在多个套餐里面,这样只能记录这个点所在的最后一个套餐里的代表元,那么会导致在先前的套餐里本来应是权值为0的边消失。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int max_n=1e6;
int x[1005],y[1005],p[1005];
int pre_u[max_n],pre_v[max_n],pre_r[max_n],u[5000],v[5000],r[5000];
int pre_w[max_n],w[5000];
int price[10];
vector<int> point[10];
int n,q;
int mcount;
int distance(int i,int j)
{
int a=x[j]-x[i];
int b=y[j]-y[i];
return (a*a+b*b);
}
bool cmp_1(const int i,const int j)
{
return pre_w[i]<pre_w[j];
}
bool cmp_2(const int i,const int j)
{
return w[i]<w[j];
}
int find(int x)
{
return p[x]==x?x:p[x]=find(p[x]);
}
long long kruskal(void)
{
long long ans=0;
for(int i=0;i<mcount;i++)r[i]=i;
for(int i=1;i<=n;i++)p[i]=i;
sort(r,r+mcount,cmp_2);
for(int i=0;i<mcount;i++)
{
int e=r[i];
int x=find(u[e]);
int y=find(v[e]);
if(x!=y){
p[x]=y;
ans+=w[e];
}
}
return ans;
}
void solve(void)
{
int cnt=0;
mcount=0;
long long ans=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)//建边
{
pre_r[cnt]=cnt;
pre_u[cnt]=i;
pre_v[cnt]=j;
pre_w[cnt++]=distance(i,j);
}
sort(pre_r,pre_r+cnt,cmp_1);
for(int i=1;i<=n;i++)p[i]=i;
for(int i=0;i<cnt;i++)
{
int e=pre_r[i];
int x=find(pre_u[e]);
int y=find(pre_v[e]);
if(x!=y){
p[x]=y;
ans+=pre_w[e];
u[mcount]=pre_u[e];
v[mcount]=pre_v[e];
w[mcount++]=pre_w[e];
}
if(mcount==n-1)break;
}//筛选出n-1条边
for(int i=1;i<(1<<q);i++)
{
mcount=n-1;
long long cost=0;
for(int j=0;j<q;j++)
if(i&(1<<j)){
for(int k=0;k<point[j].size()-1;k++)
{
u[mcount]=point[j][k];
v[mcount]=point[j][k+1];
w[mcount++]=0;
}
cost+=price[j];
}
long long temp=kruskal()+cost;
if(temp<ans)ans=temp;
}
cout<<ans<<endl;
}
int main(void)
{
// freopen("out.txt","w",stdout);
int t;
cin>>t;
while(t--)
{
scanf("%d%d",&n,&q);
for(int i=0;i<q;i++)
point[i].clear();
for(int i=0;i<q;i++)
{
int a;
scanf("%d%d",&a,&price[i]);
while(a--)
{
int b;
scanf("%d",&b);
point[i].push_back(b);
}
}
for(int i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
solve();
if(t!=0)cout<<endl;
}
// fclose(stdout);
}
//1
//
//7 3
//2 4 1 2
//3 3 3 6 7
//3 9 2 4 5
//0 2
//4 0
//2 0
//4 2
//1 3
//0 5
//4 4