基础图论知识补全计划1:Kruskal(克鲁斯卡尔)算法汇总

1.用Kruskal跑最小生成树:

        为Kruskal算法的基础应用。

        板子题   1140. 最短网络 - AcWing题库

农夫约翰被选为他们镇的镇长!

他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。

约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。

约翰的农场的编号是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跑次小生成树

        相比于最小生成树,次小生成树即为在建出最小生成树后拿一条未利用的边(未利用边中最小的)去代替最小生成树中的某一条边(连通对应两点的最大的边)。

        板子题:1148. 秘密的牛奶运输 - AcWing题库

农夫约翰要把他的牛奶运输到各个销售点。

运输过程中,可以先把牛奶运输到一些销售点,再由这些销售点分别运输到其他销售点。

运输的总距离越小,运输的成本也就越低。

低成本的运输是农夫约翰所希望的。

不过,他并不想让他的竞争对手知道他具体的运输方案,所以他希望采用费用第二小的运输方案而不是最小的。

现在请你帮忙找到该运输方案。

注意:

  • 如果两个方案至少有一条边不同,则我们认为是不同方案;
  • 费用第二小的方案在数值上一定要严格大于费用最小的方案;
  • 答案保证一定有解;

输入格式

第一行是两个整数 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数组一起更新即可 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值