算法(prim)
prim算法只与点的数量有关O(n^2)
(最小生成树) O(n2)
将题目中所有发电站和电线看成一张无向图,搭建电线看成是将图上两个点连接起来。
将发电站看成最远点(连通块始点)
根据题意,最终得到的图是若干个连通块,每个连通块中有一个点建立发电站。
考虑新建一个 0点,向 1∼n 中所有点 i 连一条长度是 Ci 的边。
那么在某个点 i 建立发电站就可以看成选择 0→i的这条边。
那么我们要做的,就转化为:给定一张无向图,选若干条边,使得图连通。
而这恰是最小生成树。因此我们只需建图并求最小生成树即可。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N = 2005; int n, m, p[N]; int x[N], y[N], k[N]; struct Edge { int a, b; ll c; inline bool operator < (const Edge& t) const { return c < t.c; } } edge[N * N]; int cnt; bool st[N * N]; // 用于记录每条边是否被选 int find(int x) { return x != p[x] ? p[x] = find(p[x]) : x; } // 求最小生成树并返回最小生成树的权值和 ll kruskal() { ll res = 0; sort(edge, edge + cnt); for (int i = 0; i < cnt; ++i) { int a = find(edge[i].a), b = find(edge[i].b); if (a != b) { p[a] = b; res += edge[i].c; st[i] = true; } } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d%d", x + i, y + i); p[i] = i; } for (int i = 1; i <= n; ++i) { int c; scanf("%d", &c); edge[cnt++] = {i, 0, c}; // 加入一条从 0 连向 i 的边 } for (int i = 1; i <= n; ++i) { scanf("%d", k + i); for (int j = 1; j < i; ++j) // 建图,将所有的边 (i, j) 连起来,边权为电线每单位花费乘长度 edge[cnt++] = {i, j, (ll)(k[i] + k[j]) * (abs(x[i] - x[j]) + abs(y[i] - y[j]))}; } printf("%lld\n", kruskal()); vector<int> v; // 存建立发电站的点 vector<pair<int, int>> es; // 存连电线的点对 for (int i = 0; i < cnt; ++i) if (st[i]) if (!edge[i].b) v.push_back(edge[i].a); // edge[i].b 是 0 说明选择了从 0 连向这个点的边,也就是建立了发电站 else es.push_back({edge[i].a, edge[i].b}); printf("%d\n", v.size()); for (int& x : v) printf("%d ", x); printf("\n%d\n", es.size()); for (auto& [x, y] : es) printf("%d %d\n", x, y); return 0; }
这里是 kruskal 算法求最小生成树,时间复杂度为 O(n2logn)
更优的算法是用 prim 算法,时间复杂度为 O(n2),但用 prim 算法不太好求具体方案,故这里选择 kruskal。
转自:
作者:垫底抽风
链接:https://www.acwing.com/solution/content/54472/
来源:AcWing
prim算法:
#include <bits/stdc++.h> #define x first #define y second using namespace std; typedef long long LL; typedef pair<int, int> PII; const int N = 2010; int n; PII q[N]; int wc[N],wk[N]; int dist[N],distp[N]; bool st[N]; vector<int> ans1; vector<PII> ans2; inline LL get_dist(int a,int b){ int dx=q[a].x-q[b].x; int dy=q[a].y-q[b].y; return (LL)(abs(dx)+abs(dy))*(wk[a]+wk[b]); } LL prim() { LL res=0; dist[0]=0; st[0]=1; for (int i = 1; i <= n; i ++ )dist[i]=wc[i]; for (int i = 0; i < n; i ++ ){ int t=-1; for (int j = 1; j <= n; j ++ ){ if(!st[j]&&(t==-1||dist[t]>dist[j])){ t=j; } } st[t]=1; res+=dist[t]; if(!distp[t])ans1.push_back(t); else ans2.push_back( {distp[t], t} ); for(int j=1 ;j<=n;j++) { if(!st[j] && dist[j] > get_dist(t,j)) { dist[j]=get_dist(t,j); distp[j]=t; } } } return res; } int main(){ scanf("%d", &n); for (int i = 1; i <= n; i ++ ) scanf("%d%d", &q[i].x,&q[i].y); for (int i = 1; i <= n; i ++ ) scanf("%d",&wc[i]); for (int i = 1; i <= n; i ++ ) scanf("%d", &wk[i]); LL res=prim(); printf("%lld\n",res); printf("%d\n",ans1.size()); for(auto x:ans1) printf("%d ",x); puts(""); printf("%d\n", ans2.size()); for(auto &t: ans2){ printf("%d %d\n",t.x,t.y); } return 0; }