题意:平面上有n个点(1<=N<=1000),你的任务是让所有n个点连通,为此,你可以新建一些边,费用等于两个端点的欧几里得距离的平方。另外还有q(0<=q<=8)个套餐,可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相互连通,第i个套餐的花费为ci。
思路:按照紫书上给的思路写的,先跑一边kruskal,把每个边存起来。枚举套餐,然后只用存起来的边跑kruskal。思路很明确和清晰。对我来说,问题在于怎么枚举子集。按照紫书的方法是二进制来枚举子集。代码如下
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn=1000+10;
struct Edge{
int u,v,w;
bool operator < (const Edge &rhs){
return w<rhs.w;
}
}edges[maxn*maxn];
vector<int>Q[10];
vector<int>edg;
int x[maxn],y[maxn],p[maxn],cost[10];
int vis[maxn][maxn];//可以优化
int n,m,q,T,ans;
int find(int x){
return p[x]==x?x:p[x]=find(p[x]);
}
int dist(int x1,int x2,int y1,int y2){
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
void build(){
memset(vis,0,sizeof(vis));
m=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(i!=j&&!vis[i][j]){
edges[m].u=i;
edges[m].v=j;
edges[m].w=dist(x[i],x[j],y[i],y[j]);
vis[i][j]=vis[j][i]=1;
m++;
}
}
}
}
int main(){
// freopen("in.txt","r",stdin);
cin>>T;
while(T--){
ans=0;
edg.clear();
cin>>n>>q;
for(int i=0;i<n;i++)Q[i].clear();
for(int i=0;i<q;i++){
int nn;
cin>>nn;
cin>>cost[i];
for(int j=0;j<nn;j++){
int a;
cin>>a;
Q[i].push_back(a-1);
}
}
for(int i=0;i<n;i++)
cin>>x[i]>>y[i];
build();
/*for(int i=0;i<m;i++)
cout<<edges[i].u<<" "<<edges[i].v<<" "<<edges[i].w<<endl;*/
sort(edges,edges+m);
for(int i=0;i<n;i++)p[i]=i;
for(int i=0;i<m;i++){
int a=find(edges[i].u),b=find(edges[i].v);
if(a!=b){
p[a]=b;
edg.push_back(i);
ans+=edges[i].w;
}
}
//cout<<ans<<endl;
for(int s=0;s<(1<<q);s++){
int ANS=0;
for(int i=0;i<n;i++)p[i]=i;
for(int i=0;i<q;i++){
if(s&(1<<i)){
ANS+=cost[i];
for(int j=0;j<Q[i].size();j++){
int a=find(Q[i][j]),b=find(Q[i][0]);
if(a!=b){
p[a]=b;
}
}
}
}
for(int i=0;i<edg.size();i++){
int e=edg[i];
int a=find(edges[e].u),b=find(edges[e].v);
if(a!=b){
p[a]=b;
ANS+=edges[e].w;
}
}
ans=min(ans,ANS);
}
cout<<ans<<endl;
if(T)cout<<""<<endl;
}
return 0;
}
我一开始写的时候用的是递归来枚举子集,但是犯了一个基础的知识错误。在参数列表传递数组时传递的也不过是个地址,并不会复制,因此在当前函数对数组修改后,调用结束需要进行“退栈”处理,既把函数的值复原,方便同层递归树的调用。
代码如下
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
const int maxn=1000+10;
int vis[maxn][maxn],cost[10],p[maxn];
long long x[maxn],y[maxn],w[maxn*maxn];
int u[maxn*maxn],v[maxn*maxn],r[maxn*maxn];
int vsq[10];
vector<int>Q[10];
vector<int>tree;
int T,n,m,q;
long long ans;
long long dist(long long x1,long long y1,long long x2,long long y2){
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
void build_edge(){
m=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
m++;
u[m]=i;v[m]=j;w[m]=dist(x[i],y[i],x[j],y[j]);
}
}
return ;
}
int cmp(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]);
}
void solve(int *A,int num){
long long ANS=0;
A[num]=1;
/*for(int i=1;i<=q;i++)
cout<<A[i]<<" ";
cout<<""<<endl;*/
for(int i=1;i<=n;i++)p[i]=i;
for(int i=1;i<=q;i++){
if(A[i]){
for(int j=0;j<Q[i].size();j++){
int a=find(Q[i][j]);
int b=find(Q[i][0]);
if(a!=b){
p[a]=b;
}
}
ANS+=cost[i];
}
}
for(int i=0;i<tree.size();i++){
int e=tree[i];
int a=find(u[e]),b=find(v[e]);
if(a!=b){
p[a]=b;
ANS+=w[e];
}
}
ans=min(ANS,ans);
for(int i=1;i<=q;i++){
if(!A[i]){
solve(A,i);
}
}
A[num]=0;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
cin>>T;
while(T--){
memset(vis,0,sizeof(vis));
memset(vsq,0,sizeof(vsq));
ans=0;
cin>>n>>q;
for(int i=1;i<=q;i++)Q[i].clear();
tree.clear();
for(int i=1;i<=q;i++){
int a,b;
cin>>a>>b;
cost[i]=b;
for(int j=1;j<=a;j++){
int aa;
cin>>aa;
Q[i].push_back(aa);
}
}
for(int i=1;i<=n;i++){
cin>>x[i]>>y[i];
}
build_edge();
//cout<<m<<endl;
/*for(int i=1;i<=m;i++){
cout<<u[i]<<" "<<v[i]<<" "<<w[i]<<endl;
}*/
for(int i=1;i<=m;i++)r[i]=i;
for(int i=1;i<=n;i++)p[i]=i;
sort(r+1,r+1+m,cmp);
/*for(int i=1;i<=m;i++){
int e=r[i];
cout<<u[e]<<" "<<v[e]<<" "<<w[e]<<endl;
}*/
for(int i=1;i<=m;i++){
int e=r[i];
int a=find(u[e]),b=find(v[e]);
if(a!=b){
p[a]=b;
tree.push_back(e);
ans+=w[e];
}
}
for(int i=1;i<=q;i++){
solve(vsq,i);
}
cout<<ans<<endl;
if(T)
cout<<""<<endl;
}
return 0;
}