1.用Kruskal跑最小生成树:
为Kruskal算法的基础应用。
农夫约翰被选为他们镇的镇长!
他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。
约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。
约翰的农场的编号是1,其他农场的编号是 2∼n2∼n。
为了使花费最少,他希望用于连接所有的农场的光纤总长度尽可能短。
你将得到一份各农场之间连接距离的列表,你必须找出能连接所有农场并使所用光纤最短的方案。
输入格式
第一行包含一个整数 nn,表示农场个数。
接下来 nn 行,每行包含 nn 个整数,输入一个对角线上全是0的对称矩阵。
其中第 x+1x+1 行 yy 列的整数表示连接农场 xx 和农场 yy 所需要的光纤长度。输出格式
输出一个整数,表示所需的最小光纤长度。
数据范围
3≤n≤1003≤n≤100
每两个农场间的距离均是非负整数且不超过100000。输入样例:
4 0 4 9 21 4 0 8 17 9 8 0 16 21 17 16 0
输出样例:
28
把每条边的边权拿出来排序,排完序后按从小到大的顺序加边,加边的同时用并查集检查,更新两点间的连通关系,如两点已连通则该边不需要加入,遍历下一个边即可,当有边加入时,即使更新并查集内容。
代码:
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
const long long maxx = 0x3f3f3f3f3f3f3f3f;
const long long minn = 0xc0c0c0c0c0c0c0c0;
const double pi = 4.0 * atan(1.0);
#define int long long
#define f(i, n, m) for (long long i = n; i <= m; ++i)
#define unf(i, n, m) for (long long i = n; i >= m; --i)
#define kong NULL
#define debug cout << "sss" << endl;
// #define map unordered_map
#define map gp_hash_table
struct ww
{
int l, r, val;
} mp[11000];
int n;
int pre[110];
int fin(int x)
{
if (x == pre[x])
return x;
return pre[x] = fin(pre[x]);
}
void solve()
{
cin >> n;
f(i, 1, n) pre[i] = i;
int cnt = 0;
f(i, 1, n)
{
f(j, 1, n)
{
if (i == j)
{
int a;
cin >> a;
continue;
}
mp[++cnt].l = i, mp[cnt].r = j;
cin >> mp[cnt].val;
}
}
sort(mp + 1, mp + cnt + 1, [](ww x, ww y)
{ return x.val < y.val; });
int ans = 0;
f(i, 1, cnt)
{
if (fin(mp[i].l) == fin(mp[i].r))
continue;
ans += mp[i].val;
pre[fin(mp[i].l)] = fin(mp[i].r);
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
solve();
return 0;
}
2. Kruskal跑次小生成树
相比于最小生成树,次小生成树即为在建出最小生成树后拿一条未利用的边(未利用边中最小的)去代替最小生成树中的某一条边(连通对应两点的最大的边)。
农夫约翰要把他的牛奶运输到各个销售点。
运输过程中,可以先把牛奶运输到一些销售点,再由这些销售点分别运输到其他销售点。
运输的总距离越小,运输的成本也就越低。
低成本的运输是农夫约翰所希望的。
不过,他并不想让他的竞争对手知道他具体的运输方案,所以他希望采用费用第二小的运输方案而不是最小的。
现在请你帮忙找到该运输方案。
注意::
- 如果两个方案至少有一条边不同,则我们认为是不同方案;
- 费用第二小的方案在数值上一定要严格大于费用最小的方案;
- 答案保证一定有解;
输入格式
第一行是两个整数 N,MN,M,表示销售点数和交通线路数;
接下来 MM 行每行 33 个整数 x,y,zx,y,z,表示销售点 xx 和销售点 yy 之间存在线路,长度为 zz。
输出格式
输出费用第二小的运输方案的运输总距离。
数据范围
1≤N≤5001≤N≤500,
1≤M≤1041≤M≤104,
1≤z≤1091≤z≤109,
数据中可能包含重边。输入样例:
4 4 1 2 100 2 4 200 2 3 250 3 4 100
输出样例:
450
判断该题要求的是严格次小,因此需要记录最小生成树中每两点间的最大边跟次大边,当未利用边权值跟最大边相等时,不能拿其替换,(因为要求严格次小,这样换完值不变),因此要拿次大边跟未利用边换,而当未利用边大于最大边时,直接换最大边即可。
操作步骤:
1.跑最小生成树
2.跑dfs存每两点的最大边跟次大边
3.遍历未利用的边,记录替换后跟原来的差值,找最小的即可
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
const long long maxx = 0x3f3f3f3f3f3f3f3f;
const long long minn = 0xc0c0c0c0c0c0c0c0;
const double pi = 4.0 * atan(1.0);
#define int long long
#define f(i, n, m) for (long long i = n; i <= m; ++i)
#define unf(i, n, m) for (long long i = n; i >= m; --i)
#define kong NULL
#define debug cout << "sss" << endl;
// #define map unordered_map
int n, m;
struct ww
{
int l, r, val;
} pos[10100];
int pre[510];
int fin(int x)
{
if (x == pre[x])
return x;
return pre[x] = fin(pre[x]);
}
//链前存图
struct www
{
int next, to, val;
} ppre[20010];
int cnt = 1;
int head[510];
void add(int x, int y, int z)
{
ppre[cnt].next = head[x];
ppre[cnt].to = y;
ppre[cnt].val = z;
head[x] = cnt++;
}
map<pair<int, int>, pair<int, int>> mp; //两个点,最大和次大边
//找最大和次大边:
// void dfs(int x, int fa, int ji)
// {
// for (int i = head[x]; i; i = ppre[i].next)
// {
// int to = ppre[i].to;
// int val = ppre[i].val;
// if (to == fa)
// continue;
// if (val > mp[{ji, to}].first)
// {
// mp[{ji, to}].second = mp[{ji, to}].first;
// mp[{ji, to}].first = val;
// mp[{to, ji}] = mp[{ji, to}];
// }
// else if (val > mp[{ji, to}].second&&val<mp[{ji,to}].first)
// {
// mp[{ji, to}].second = val;
// mp[{to, ji}].second = mp[{ji, to}].second;
// }
// dfs(to, x, ji);
// }
// }
void dfs(int s, int u, int fa, int mw1, int mw2) {
//从s起点出发到其他点
mp[{s,u}].first = mw1; mp[{s,u}].second = mw2;
for (int i = head[u]; i; i = ppre[i].next){
int to = ppre[i].to;
int w = ppre[i].val;
if (to!= fa) {
// if (w > mp[{s, to}].first)
// {
// mp[{s, to}].second = mp[{s, to}].first;
// mp[{s, to}].first = w;
// mp[{to, s}] = mp[{s, to}];
// }
// else if (w > mp[{s, to}].second&&w<mp[{s,to}].first)
// {
// mp[{s, to}].second = w;
// mp[{to, s}].second = mp[{s, to}].second;
// }
int t1 = mw1, t2 = mw2;
if (w > mw1) t1 = w, t2 = mw1;
//这里不能相等
else if (w < mw1 && w > mw2) t2 = w;
dfs(s, to, u, t1, t2);
}
}
}
void solve()
{
int fact = 0;
cin >> n >> m;
f(i, 1, n) pre[i] = i;
f(i, 1, m)
{
cin >> pos[i].l >> pos[i].r >> pos[i].val;
}
sort(pos + 1, pos + m + 1, [](ww x, ww y)
{ return x.val < y.val; });
f(i, 1, m)
{
if (fin(pos[i].l) == fin(pos[i].r))
continue;
add(pos[i].l, pos[i].r, pos[i].val);
add(pos[i].r, pos[i].l, pos[i].val);
pre[fin(pos[i].l)] = fin(pos[i].r);
fact += pos[i].val;
pos[i].val = -1;
}
//存图结束
//找最大和次大边
f(i, 1, n)
{
mp[{i,i}].first=mp[{i,i}].second=0;
dfs(i, i, -1, 0, 0);
}
//遍历非树边,找差值
int cha = maxx;
f(i, 1, m)
{
if (pos[i].val == -1)
continue;
int l = pos[i].l, r = pos[i].r;
if (mp[{l, r}].first < pos[i].val)
cha = min(cha, fact+pos[i].val - mp[{r, l}].first);
else if(pos[i].val>mp[{l,r}].second)
cha = min(cha, fact+pos[i].val - mp[{r, l}].second);
}
if (cha)
cout << cha << endl;
}
signed main()
{
ios::sync_with_stdio(false);
solve();
return 0;
}
3.Kruskal重构树
用于求解某一点到另一点所经过的边权中权值最大的边是多大
实现过程:
1.先跑一个最小生成树,确保路径唯一
2.把跑出来是最小生成树的每一个节点看作二叉树的叶子节点,升序排列所有边权,在其对应的两点间建立父亲节点,其权值记为这两点的边权值,查询时跑两个点的lca,找出的点的权值即为所求答案
板子例题:P2245 星际导航 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
代码实现:
#include <bits/stdc++.h>
using namespace std;
const long long maxx = 0x3f3f3f3f3f3f3f3f;
const long long minn = 0xc0c0c0c0c0c0c0c0;
const double pi = 4.0 * atan(1.0);
#define f(i, n, m) for (long long i = n; i <= m; ++i)
#define unf(i, n, m) for (long long i = n; i >= m; --i)
#define kong NULL
#define debug cout << "sss" << endl;
inline int read()
{
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar())
if (c == '-')
w = -1;
for (; isdigit(c); c = getchar())
s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
int n, m;
int pre[800010];
int fin( int x)
{
if (x == pre[x])
return x;
return pre[x] = fin(pre[x]);
}
struct ww
{
int a, b, val;
} bian[800010];
inline bool pan(ww x, ww y)
{
return x.val < y.val;
};
//链前
int head[800010];
struct www
{
int next, to, w;
} pos[800010];
int cnt = 1;
void add( int x, int y, int z)
{
pos[cnt].to = y;
pos[cnt].next = head[x];
pos[cnt].w = z;
head[x] = cnt++;
}
int quan[800010];
int h[800010][21];
// lca处理
int fa[800010][21];
int deap[800010];
void dfs( int u, int faa, int w)
{
deap[u] = deap[faa] + 1;
fa[u][0] = faa, h[u][0] = w;
for (register int i = 1; i <= 20; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1],
h[u][i] = max(h[u][i - 1], h[fa[u][i - 1]][i - 1]);
for (register int i = head[u]; i; i = pos[i].next)
{
int v = pos[i].to;
if (v != faa)
dfs(v, u, pos[i].w);
}
}
inline int LCA(int u, int v)
{
int ans = 0;
if (deap[u] < deap[v])
swap(u, v);
for (register int i = 20; i >= 0; i--)
if (deap[fa[u][i]] >= deap[v])
{
ans = max(ans, h[u][i]);
u = fa[u][i];
}
if (u == v)
return ans;
for (register int i = 20; i >= 0; i--)
if (fa[u][i] != fa[v][i])
{
ans = max(ans, h[u][i]), ans = max(ans, h[v][i]);
u = fa[u][i], v = fa[v][i];
}
ans = max(ans, h[u][0]), ans = max(ans, h[v][0]);
return ans;
}
map< int, int> lian;
inline void kruskal(){
sort(bian + 1, bian + m + 1, pan);
for (register int i = 1; i <= m; i++)
{
int eu = fin(bian[i].a), ev = fin(bian[i].b);
if (eu == ev)
continue;
add(bian[i].a, bian[i].b, bian[i].val), add(bian[i].b, bian[i].a, bian[i].val);
pre[ev] = eu;
// ++tot;
// if (tot == n - 1) break;
}
}
inline void Init()
{
for (register int i = 1; i <= n; i++)
pre[i] = i;
}
void solve()
{
n=read();
m=read();
f(i, 1, m)
{
bian[i].a=read(); bian[i].b =read(); bian[i].val=read();
}
Init();
kruskal();
// lca处理
for (register int i = 1; i <= n; i++)
if (!deap[i])
dfs(i, 0, 0);
//输出
int o;
cin >> o;
f(i, 1, o)
{
int a, b;
a=read(); b=read();
if (fin(a) != fin(b))
printf("impossible\n");
else
printf("%d\n", LCA(a, b));
}
}
signed main()
{
solve();
return 0;
}
注意:在写代码时并不用真正去建二叉树,只要开个数组h记录该点倍增上去对应的点的权值即可。跟着lca中拿来跑倍增的fa数组一起更新即可