最小生成树
\(\text{luogu-2330}\)
给定一张 \(n\) 个点 \(m\) 条边的无向有权图,求这张图的最小生成树中最大边权。
\(1 \le n \le 300\),\(1 \le w \le 10^4\)。
直接求最小生成树即可。
\(\text{luogu-2504}\)
给定 \(n\) 只猴子的跳跃距离 \(s_i\) 和 \(m\) 棵树的坐标 \((x_i, y_i)\),求有多少只猴子可以通过一棵树到达所有树。
\(2 \le n \le 500\),\(2 \le m \le 1000\),\(-1000 \le x_i, y_i \le 1000\),\(1 \le s_i \le 1000\)。
预处理出所有树之间的距离,跑最小生成树过程中记录最大距离。
若一只猴子的跳跃距离大于等于最大距离就累加到答案中即可。
\(\text{hdu-1301}\)
给定一张城市地图,留下最少的边权和使得图仍联通。
\(1 < n < 27\)。
输入格式比较猎奇,其他没啥。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 10005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long x, y, w; } e[MAXN];
long long n, m, fa[MAXN];
bool cmp(node l, node r) { return l.w < r.w; }
long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
long long Kruskal() {
long long res = 0;
sort(e + 1, e + m + 1, cmp);
for(int i = 1; i <= m; i ++) {
long long x = find(e[i].x), y = find(e[i].y);
if(x == y) continue;
fa[y] = x, res += e[i].w;
}
return res;
}
int main() {
while(cin >> n) {
m = 0; if(!n) break;
for(int i = 0; i < n - 1; i ++) {
long long x, y; char c;
cin >> c >> x;
for(int j = 1; j <= x; j ++) {
cin >> c >> y;
e[++ m] = {i, c - 'A', y};
}
}
for(int i = 0; i < 26; i ++) fa[i] = i;
cout << Kruskal() << "\n";
}
return 0;
}
\(\text{poj-1287}\)
最小生成树板子,没什么特别的。
\(\text{poj-2253}\)
给定 \(n\) 个点的坐标 \((x_i, y_i)\),要求 \(1,2\) 号点联通且路径上的距离最大值最小。
\(2 \le n \le 200\),\(0 \le x_i, y_i \le 1000\)。
只需要处理 \(1,2\) 号点的连通性即可,若 \(1,2\) 已经联通则直接 break 即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 100005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long x, y; double w; } e[MAXN];
long long n, m, x[MAXN], y[MAXN], fa[MAXN], cnt, mk;
long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
bool cmp(node l, node r) { return l.w < r.w; }
double Kruskal(long long m) {
double res = 0;
sort(e + 1, e + m + 1, cmp);
for(int i = 1; i <= m; i ++) {
long long x = find(e[i].x), y = find(e[i].y);
if(x == y) continue;
fa[y] = x, res = max(res, e[i].w);
if(find(1) == find(2)) break;
}
return res;
}
int main() {
while(n = read()) {
cnt = 0;
for(int i = 1; i <= n; i ++) x[i] = read(), y[i] = read();
for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++)
e[++ cnt].x = i, e[cnt].y = j, e[cnt].w = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
for(int i = 1; i <= n; i ++) fa[i] = i;
printf("Scenario #%lld\nFrog Distance = %.3lf\n\n", (++ mk), Kruskal(cnt));
}
return 0;
}
\(\text{hdu-1863}\)
给定一张 \(n\) 个点 \(m\) 条边的无向有权图,求最小生成树的边权和,若无法联通则输出 ?。
\(1 \le n < 100\)。
记录一个边数即可,若最小生成树的边数小于 \(n-1\) 则说明图不连通。
\(\text{hdu-1879}\)
给定 \(n\) 个城市和 \(\frac{n(n-1)}{2}\) 条道路,有些道路是建好的,求要求城市两两联通需要的修建费用。
\(1 < n < 100\)。
若道路已经建好则不需要加入图中,只需要更新一下两个点的并查集即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 100005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long x, y, w; } e[MAXN];
long long n, m, fa[MAXN], cnt;
long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
bool cmp(node l, node r) { return l.w < r.w; }
long long Kruskal() {
long long res = 0;
sort(e + 1, e + cnt + 1, cmp);
for(int i = 1; i <= cnt; i ++) {
long long x = find(e[i].x), y = find(e[i].y);
if(x == y) continue;
fa[y] = x, res += e[i].w;
}
return res;
}
int main() {
while(n = read()) {
m = (n - 1) * n / 2, cnt = 0;
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++) {
long long x = read(), y = read(), w = read(), p = read();
if(p) fa[find(y)] = find(x);
else e[++ cnt].x = x, e[cnt].y = y, e[cnt].w = w;
}
cout << Kruskal() << "\n";
}
return 0;
}
\(\text{luogu-2872}\)
给定 \(n\) 个点的坐标和 \(m\) 条已连接的边,求图联通至少需要连接的距离和。
\(1 \le n,m \le 1000\),\(1 \le x_i, y_i \le 10^6\),\(1 \le u_i, v_i \le n\)。
和 \(\text{hdu-1879}\) 几乎一样,不写了。
\(\text{luogu-4047}\)
给定 \(n\) 个部落的坐标,将这些部落划分成 \(k\) 个群落,求最大的最近群落距离。
\(2 \le k \le n \le 1000\),\(0 \le x_i, y_i \le 10^4\)。
考虑跑一下最小生成树,对于生成树上前 \(k-1\) 大的边作为部落的分割线。
答案也就是最小生成树上第 \(n - k + 1\) 小的边权。
证明可以感性证明,不写了。
\(\text{poj-1789}\)
给定 \(n\) 个长度为 \(7\) 的字符串,定义字符串的距离为相同位置不同字符的个数。
求所有字符串距离的最小生成树的边权和。
\(2 \le n \le 2000\)。
模板题,不想写。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAXN 2005
int read() {
int x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { int x, y, w; } e[MAXN * MAXN];
int n, cnt, fa[MAXN], ans;
char s[MAXN][10];
bool cmp(node l, node r) { return l.w < r.w; }
int find(int x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
void Kruskal() {
sort(e + 1, e + cnt + 1, cmp);
for(int i = 1; i <= cnt; i ++) {
int x = find(e[i].x), y = find(e[i].y);
if(x == y) continue;
fa[y] = x, ans += e[i].w;
}
return;
}
int main() {
while(n = read()) {
cnt = ans = 0;
for(int i = 1; i <= n; i ++) cin >> (s[i] + 1);
for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++) {
int res = 0;
for(int k = 1; k <= 7; k ++) if(s[i][k] != s[j][k]) res ++;
e[++ cnt].x = i, e[cnt].y = j, e[cnt].w = res;
}
for(int i = 1; i <= n; i ++) fa[i] = i;
Kruskal();
cout << "The highest possible quality is 1/" << ans << ".\n";
}
return 0;
}
\(\text{poj-2485}\)
求最小生成树上的最大边权。
\(3 \le n \le 500\)。
模板题。
\(\text{luogu-1546}\)
求最小生成树的边权和。
\(3 \le n \le 100\)。
模板题。
\(\text{hdu-4081}\)
给出 \(n\) 个点的城市坐标与城市的人口,要求将所有城市连通起来,其中有一条道路是魔法道路没有边权,要求这条道路连接的两城市的人口数和为 \(A\),其余道路权值和为 \(B\),求 \(\frac{A}{B}\) 的最大值。
\(1 \le T \le 10\),\(2 \le n \le 1000\),\(0 \le x_i, y_i \le 1000\),\(0 < p < 10^5\)。
次小生成树思想,枚举并删边。
先根据城市坐标求出任两个城市相连道路的边权,然后根据边权求最小生成树,由于连接两个人口数最大的城市的道路没有边权,因此删掉所枚举的边后剩余的边权和是固定的,即 \(B\) 是固定的,因而可以枚举所有边,确立边两端的点的人口数之和,寻找最大的人口数之和即 \(A\),除枚举的边之外的边权和即 \(B\)。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXN 1005
int read() {
int x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
int T, n, x[MAXN], y[MAXN], p[MAXN], fa[MAXN], cnt, s[MAXN];
struct node { int x, y, w; } e[MAXN * MAXN];
vector<long long> v[MAXN];
bool vis[MAXN];
double ans;
bool cmp(node l, node r) { return l.w < r.w; }
int find(int x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
void Kruskal() {
sort(e + 1, e + cnt + 1, cmp);
long long res = 0;
for(int i = 1; i <= cnt; i ++) {
long long x = find(e[i].x), y = find(e[i].y);
if(x == y) continue;
fa[y] = x, s[++ res] = i;
v[e[i].x].push_back(e[i].y);
v[e[i].y].push_back(e[i].x);
}
return;
}
double dfs(long long x, double res) {
vis[x] = true, res = max(res, 1.0 * p[x]);
for(auto y : v[x]) if(!vis[y]) res = dfs(y, res);
return res;
}
int main() {
T = read();
while(T --) {
n = read(), cnt = 0, ans = -1e18;
for(int i = 1; i <= n; i ++)
x[i] = read(), y[i] = read(), p[i] = read();
for(int i = 1; i <= n; i ++) v[i].clear();
for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++)
e[++ cnt].x = i, e[cnt].y = j, e[cnt].w = (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
for(int i = 1; i <= n; i ++) fa[i] = i;
Kruskal(); double dis = 0;
for(int i = 1; i < n; i ++) dis += sqrt(e[s[i]].w);
for(int i = 1; i < n; i ++) {
for(int j = 1; j <= n; j ++) vis[j] = false;
vis[e[s[i]].y] = true;
double y = dfs(e[s[i]].x, 0) + dfs(e[s[i]].y, 0);
ans = max(ans, y / (dis - sqrt(e[s[i]].w)));
}
printf("%.2lf\n", ans);
}
return 0;
}
\(\text{poj-2728}\)
给定 \(n\) 个点的坐标和点权,任意两点之间的边的价值是它们的距离,费用是两点权值之差的绝对值,求该图的一棵生成树,使得该树所有边的费用之和与价值之和的比值最小(只需求这个比值即可)。
\(2 \le n \le 1000\),\(0 \le x_i, y_i \le 10^4\),\(0 \le w_i \le 10^9\)。
事实上就是求下式的最小值,\(w_i \in \{0, 1\}\):
这类问题称为分数规划,通用方法就是二分答案,设当前二分的答案为 \(mid\),则:
即不等式成立则说明 \(mid\) 合法,因此,这道题要求最小值,也就是求 \(mid\) 合法最大值。
于是二分答案,然后用 \(\text{Prim}\) 算法 \(O(n^2)\) \(\text{check}\) 即可。
总体时间复杂度为 \(O(n^2 \log (\sum w_i))\)。
注意:这道题 \(\text{Kruskal}\) 会 \(\text{TLE}\);不要用 \(\text{memset()}\) 初始化 \(\text{double}\) 变量 \(\dots\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;
#define MAXN 5005
int read() {
int x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
int n, x[MAXN], y[MAXN], p[MAXN];
double dis[MAXN];
bool vis[MAXN];
bool Prim(double mid) {
for(int i = 1; i <= n; i ++) dis[i] = 1e18, vis[i] = false;
dis[1] = 0; double res = 0;
for(int i = 1; i < n; i ++) {
int mk = 0;
for(int j = 1; j <= n; j ++) if(!vis[j] &&
(dis[j] < dis[mk] || !mk)) mk = j;
vis[mk] = true;
for(int j = 1; j <= n; j ++) if(!vis[j])
dis[j] = min(dis[j], abs(p[j] - p[mk]) - mid * sqrt((x[mk] - x[j]) * (x[mk] - x[j]) + (y[mk] - y[j]) * (y[mk] - y[j])));
}
for(int i = 2; i <= n; i ++) res += dis[i];
return (res >= 0);
}
int main() {
while(cin >> n) {
if(!n) break;
for(int i = 1; i <= n; i ++) cin >> x[i] >> y[i] >> p[i];
double l = 0, r = 1e9, eps = 1e-8;
while(r - l >= eps) {
double mid = (l + r) / 2.0;
if(Prim(mid)) l = mid;
else r = mid;
}
printf("%.3lf\n", l);
}
return 0;
}
\(\text{luogu-1669}\)
求最大生成树的边权和。
\(2 \le n \le 1000\),\(1 \le m \le 2 \times 10^4\),\(1 \le w_i \le 10^5\)。
模板题。
\(\text{hdu-4750}\)
给定 \(n\) 个点 \(m\) 条边的无向图。
有 \(q\) 个询问 \(x\),求有多少二元组 \((i, j)\) 满足 \(i\) 到 \(j\) 的所有路径上的最大边权的最小值大于等于 \(x\)。
\(1 \le n \le 10^4\),\(1 \le m \le 5 \times 10^5\),\(1 \le q \le 10^5\)。
\((i, j)\) 所有路径上的最大边权的最小值实际上就是 \(i\) 到 \(j\) 在最小生成树上路径的最大边权。
注意到每条边的权值都是不相等的,那么最小生成树就是确定的。
假设当前 \(\text{MST}\) 的边的权值是 \(f_i\),\(\text{Kruskal}\) 的并查集中维护一个 \(cnt_i\),表示以节点 \(i\) 为根的集合的节点个数,那么两个集合 \(x\) 和 \(y\) 合并,点对数就增加 \(cnt_{fa_x} \times cnt_{fa_y} \times 2\)。
记 \(s\) 为点对数的累计和,那么询问 \(x\) 小于等于下一个 \(\text{MST}\) 中的边 \(f_{i+1}\) 的值就是 \(2n(n-1) - s\)。
离线处理询问即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 500005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, q, fa[MAXN], cnt[MAXN], ans[MAXN];
struct node { long long x, y, w; } e[MAXN];
struct query { long long x, id; } a[MAXN];
bool cmp1(query l, query r) { return l.x < r.x; }
bool cmp2(node l, node r) { return l.w < r.w; }
long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
int main() {
while(cin >> n >> m) {
for(int i = 1; i <= m; i ++)
e[i].x = read() + 1, e[i].y = read() + 1, e[i].w = read();
q = read();
for(int i = 1; i <= q; i ++) a[i].x = read(), a[i].id = i;
sort(a + 1, a + q + 1, cmp1);
sort(e + 1, e + m + 1, cmp2);
for(int i = 1; i <= n; i ++) fa[i] = i, cnt[i] = 1;
long long t = 0, k = 1, j = 1;
for(int i = 1; i <= m && k < n; i ++) {
long long x = find(e[i].x), y = find(e[i].y);
if(x == y) continue;
while(j <= q && a[j].x <= e[i].w)
ans[a[j].id] = n * (n - 1) - t, j ++;
t += cnt[x] * cnt[y] * 2;
fa[x] = y, cnt[y] += cnt[x], k ++;
}
while(j <= q) ans[a[j].id] = n * (n - 1) - t, j ++;
for(int i = 1; i <= q; i ++) cout << ans[i] << "\n";
}
return 0;
}
\(\text{hdu-3938}\)
两点之间建立传送门需要的能量为他们之间所有路径里最小的 \(T\),一条路径的T为该路径上最长的边的长度。现在 \(q\) 个询问,问 \(x\) 能量可以选择多少种不同点对?
\(1 < n \le 10^4\),\(1 \le m \le 5 \times 10^4\),\(0 \le x \le 10^8\)。
因为小的能量找出的点对,在大的能量下肯定也能建立传送门,因此把询问记下来,按询问的能量从小到大计算,这样离线处理。
从小到大枚举添加每条能量不超过当前能量且还没枚举过的边,
如果它连接的两个点属于不同联通块,就加上这条边。
能选择的点对就有两个联通块的点数之积那么多种。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 500005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, q, fa[MAXN], cnt[MAXN], ans[MAXN];
struct node { long long x, y, w; } e[MAXN];
struct query { long long x, id; } a[MAXN];
bool cmp1(query l, query r) { return l.x < r.x; }
bool cmp2(node l, node r) { return l.w < r.w; }
long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
int main() {
while(cin >> n >> m >> q) {
for(int i = 1; i <= m; i ++)
e[i].x = read(), e[i].y = read(), e[i].w = read();
for(int i = 1; i <= q; i ++) a[i].x = read(), a[i].id = i;
sort(a + 1, a + q + 1, cmp1);
sort(e + 1, e + m + 1, cmp2);
for(int i = 1; i <= n; i ++) fa[i] = i, cnt[i] = 1;
long long l = 1;
for(int i = 1; i <= q; i ++) {
ans[a[i].id] = ans[a[i - 1].id];
while(l <= m && e[l].w <= a[i].x) {
long long x = find(e[l].x), y = find(e[l].y);
if(x != y) {
fa[y] = x, ans[a[i].id] += cnt[x] * cnt[y];
cnt[x] += cnt[y];
}
l ++;
}
}
for(int i = 1; i <= q; i ++) cout << ans[i] << "\n";
}
return 0;
}
\(\text{hdu-6187}\)
有 \(n\) 个塔和 \(m\) 面墙,塔和塔之间有墙,这些墙把城市分隔开了。
拆第 \(i\) 面墙需要花费 \(w_i\) 代价,求使得所有城市连通的最小代价。
墙只在端点相交。可以保证没有墙连接相同的塔,也没有两面墙连接相同的一对塔。也就是说,由墙和塔形成的给定图不包含任何多重边或自环。
\(3 \le n \le 10^5\),\(1 \le m \le 2 \times 10^5\),\(|x_i|,|y_i| \le 10^5\),\(0 \le w_i \le 10^4\)。
诈骗题,实际上塔的坐标没有一点用。
求最小代价联通城市,等价于求最大代价联通塔,跑最大生成树即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 200005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { long long x, y, w; } e[MAXN];
long long n, m, x, y, fa[MAXN];
bool cmp(node l, node r) { return l.w > r.w; }
long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
pair<long long, long long> Kruskal() {
for(int i = 1; i <= n; i ++) fa[i] = i;
sort(e + 1, e + m + 1, cmp);
long long res = 0, cnt = 0;
for(int i = 1; i <= m; i ++) {
long long x = find(e[i].x), y = find(e[i].y);
if(x == y) continue;
fa[y] = x, res += e[i].w, cnt ++;
}
return {cnt, res};
}
int main() {
while(cin >> n >> m) {
for(int i = 1; i <= n; i ++) x = read(), y = read();
for(int i = 1; i <= m; i ++)
e[i].x = read(), e[i].y = read(), e[i].w = read();
long long ans = 0;
for(int i = 1; i <= m; i ++) ans += e[i].w;
pair<long long, long long> p = Kruskal();
cout << m - p.first << " " << ans - p.second << "\n";
}
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19333264

浙公网安备 33010602011771号