CF1245D题解
与其他题解不同的做法。
首先我们按照题目要求,在
回忆 kruskal 算法的过程,我们先将边从小到大排列,假设每个点是一个孤立的子集,若一条边连接
本题与裸的最小生成树的不同之处就在与每个点有一个可以选择的权值。很容易想到,对于一个连通块,我们仅选择权值最小的那个点建造发电站是最优的。
因此考虑和 kruskal 相同的过程,我们先在每个点上都建一个发电站,将边从小到大排列后依次枚举,若将两个连通块连上,取权值最小点建造发电站比在两个连通块上分别建造连通块更优,则我们在这两点间连接电缆。
考虑什么情况下在两点间连接电缆更优。设边
比较二者大小,当
考虑代码实现,我们需要做一次正常的 kruskal,即因为图的最小生成树是不变的,我们要考虑的是在哪些点上建造发电站和选取哪些边连接电缆,不可能选取原来不在最小生成树上的边连接,原因根据最优性显然。所以我们另外开一个并查集记录当前连接电缆情况,在两点之间不用电缆连接更优时,我们也需要在最小生成树对应的并查集上合并两点所在集合,代表已经考虑过是否连接电缆,此时的
时间复杂度为
#include<bits/stdc++.h>
using namespace std;
int n,xi[2005],yi[2005],c[2005],k[2005];
struct edge{
int u,v;
long long w;
}e[4000005];
int fa[2005],link[2005];
int tot=0;
bool s[2005];
long long ans=0;
vector<pair<int,int> > v_edge;
bool cmp(edge x,edge y){return x.w<y.w;}
int find(int k){return (fa[k]==k)?k:fa[k]=find(fa[k]);}
int find_link(int k){return (link[k]==k)?k:link[k]=find(link[k]);}
void print(){
cout<<ans<<"\n";
int res=0;
vector<int> v_res;
for(int i=1;i<=n;i++) s[find_link(i)]=true;
for(int i=1;i<=n;i++)
if(s[i]) v_res.push_back(i),res++;
cout<<res<<"\n";
for(int i=0;i<res;i++) cout<<v_res[i]<<" ";
cout<<"\n";
cout<<n-res<<"\n";
for(int i=0;i<n-res;i++) cout<<v_edge[i].first<<" "<<v_edge[i].second<<"\n";
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>xi[i]>>yi[i];
for(int i=1;i<=n;i++) cin>>c[i];
for(int i=1;i<=n;i++) cin>>k[i];
for(int i=1;i<=n;i++){
fa[i]=link[i]=i,ans+=1ll*c[i];
for(int j=1;j<=n;j++)
e[++tot].u=i,e[tot].v=j,e[tot].w=1ll*(k[i]+k[j])*(abs(xi[i]-xi[j])+abs(yi[i]-yi[j]));
}
sort(e+1,e+tot+1,cmp);
for(int i=1;i<=tot;i++){
if(find(e[i].u)==find(e[i].v)) continue;
int fu=find_link(e[i].u),fv=find_link(e[i].v);
if(c[fu]<c[fv]) swap(fu,fv);
if(c[fu]>=e[i].w){
ans+=e[i].w-c[fu],link[fu]=fv;
v_edge.push_back(make_pair(e[i].u,e[i].v));
}
fa[fu]=fv;
}
print();
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
/*int tt;cin>>tt;while(tt--)*/solve();
return 0;
}