题意:给你n个点和一些集合,在集合中的点是相互联通的,现在需要修建一些路使得n个点相互联通,其中每条路你可以修建也可以买,如果修建的话花费是两个点之间的欧几里得距离,其中点是在二维坐标中。每个点的位置都给出了,如果要修建的这条边在某个集合中,你也可以选择把这个集合买下来,每个集合的花费也给出了。
思路:先根据每个点的最坐标求出两点之间的欧几里得距离,再在每两点之间修建道路,先不买任何集合求出此时最小的花费,此时就是求图的最小生成树,然后在二进制枚举,因为已经告诉你集合的个数不超过8个,所以可以进行二进制枚举,看买那些边,如果要是买下其中一些集合的话,使用并查集把这些点都加到一起。然后在根据把边加到一起之后在求最小生成树,这时求最小生成树时就爱直接根据不买任何集合时的最小生成树求,因为求最小的花费所使用的肯定是最小生成树里的一些边。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+50;
int n,m;
int cnt;
int c;
struct Point
{
int x,y;
} p[maxn];
struct Node
{
int x,y,w;
} node[maxn],mst[maxn];
struct Team
{
int val,num;
int v[maxn];
} sub[10];
int fa[maxn];
void Init()
{
for(int i=0; i<=n; i++)
fa[i]=i;
}
long long int Dig(int x,int y)
{
long long int xx=p[x].x-p[y].x;
long long int yy=p[x].y-p[y].y;
return (long long int)(xx*xx+yy*yy);
}
int Find_x(int x)
{
return fa[x]=(x==fa[x]?x:Find_x(fa[x]));
}
long long int solve()
{
long long int res=0;
for(int i=0; i<c; i++)
{
int rx=Find_x(mst[i].x);
int ry=Find_x(mst[i].y);
if(rx==ry) continue;
else
{
res+=mst[i].w;
fa[rx]=fa[ry];
}
}
return res;
}
bool cmp(Node n1,Node n2)
{
return n1.w<n2.w;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
// if(T>0) cout<<endl;
scanf("%d%d",&n,&m);
Init();
for(int i=0; i<m; i++)
{
scanf("%d%d",&sub[i].num,&sub[i].val);
for(int j=0; j<sub[i].num; j++)
scanf("%d",&sub[i].v[j]);
}
for(int i=1; i<=n; i++)
scanf("%d%d",&p[i].x,&p[i].y);
cnt=0;
c=0;
for(int i=1; i<=n; i++)
{
for(int j=i+1; j<=n; j++)
{
node[cnt].x=i;
node[cnt].y=j;
node[cnt].w=Dig(i,j);
cnt++;
}
}
long long int sum=0;
sort(node,node+cnt,cmp);
for(int i=0; i<cnt; i++)
{
int rx=Find_x(node[i].x);
int ry=Find_x(node[i].y);
if(rx!=ry)
{
fa[rx]=ry;
sum+=node[i].w;
mst[c++]=node[i];
}
}
// cout<<"c "<<c<<endl;
long long int res=0;
for(int s=0; s<(1<<m); s++)
{
Init();
res=0;
for(int i=0; i<m; i++)
{
if(s&(1<<i))
{
res+=sub[i].val;
for(int j=1; j<sub[i].num; j++)
{
fa[Find_x(sub[i].v[j])]=Find_x(sub[i].v[j-1]);/////////
}
}
}
res+=solve();
sum=min(sum,res);
}
cout<<sum<<endl;
if(T) cout<<endl;
}
}
6961

被折叠的 条评论
为什么被折叠?



