[题集]prim、kruskal

思路图

稀疏图kruskal算法克鲁斯卡尔算法会比堆优化版Prim算法算法思路清楚并且代码简短很多。
稠密图朴素版Prim算法
染色法,一个很简单的深度优先遍历。


# Prim

一、1140. 最短网络(热身题)

题目

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int g[N][N];
int dist[N];
bool st[N];

int prim() {
    int ans = 0;
    memset(dist, 0x3f, sizeof dist); dist[1] = 0;
    for(int i = 1; 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;
        if(t != 1) ans += dist[t];
        for(int j = 1; j <= n; j ++) dist[j] = min(dist[j], g[t][j]);
    }
    return ans;
}

int main() {
    cin >> n;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++) 
            scanf("%d", &g[i][j]);
    cout << prim();
    return 0;
}

二、1141. 局域网(有多个联通块)

题目

根据题目可以得知,这道题有多个联通块
怎么办呢?
如果遇到一个不与之前联通块连通的点(其表现为dist[x]仍为0x3f3f3f3f)
那么就新开一个联通块,它和之前的联通块的距离就忽略不计好了。(只要在算距离和的时候加个特判条件就可以了)

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int g[N][N];
int dist[N];
bool st[N];

int prim() {
    int ans = 0;
    memset(dist, 0x3f, sizeof dist); 
    dist[1] = 0;
    for(int i = 1; i <= n; i ++) {
        int t = -1;
        for(int j = 1; j <= n; j ++) 
            if(!st[j] && (t == -1 || dist[t] > dist[j])) 
                t = j;
        // cout << t << endl;
        st[t] = 1;
        if(dist[t] != 0x3f3f3f3f) ans += dist[t];
        // cout << dist[t] << endl;
        for(int j = 1; j <= n; j ++) dist[j] = min(dist[j], g[t][j]);
    }
    return ans;
}

int main() {
    memset(g, 0x3f, sizeof g);
    int k, sum = 0;
    cin >> n >> k;
    for(int i = 1; i <= k; i ++) {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        g[x][y] = g[y][x] = z;
        sum += z;
    }
    cout << sum - prim();
    return 0;
}

三、1142. 繁忙的都市(关于最小生成树的性质)

题目

既然有了提示是最小生成树,那么我们可以从最小生成树的性质出发,看看能不能求题目的答案:
1.求联通图中最大的那条边权值最小:
显然,如果能求出最小生成树,那么这些边加起来是答案最小的,而能够使答案最小的边一定是满足联通的前提下,每两个点之间最小的边。那么显然,在这些最小的边中最大的那条边一定小于其他答案所得到的最大边权值。
2.求联通图有几条边?
由于有了提示,我们不用动脑子就可以知道是点数减一。
(但是如果没有提示,能够在第一时间想到用最小生成树吗?

#include<bits/stdc++.h>
using namespace std;
const int N = 310;
int n;
int g[N][N];
int dist[N];
bool st[N];

int prim() {
    int ans = -1;
    memset(dist, 0x3f, sizeof dist); 
    dist[1] = 0;
    for(int i = 1; i <= n; i ++) {
        int t = -1;
        for(int j = 1; j <= n; j ++) 
            if(!st[j] && (t == -1 || dist[t] > dist[j])) 
                t = j;
        // cout << t << endl;
        st[t] = 1;
        //更新答案
        if(dist[t] != 0x3f3f3f3f) ans = max(ans, dist[t]);
        // cout << dist[t] << endl;
        for(int j = 1; j <= n; j ++) dist[j] = min(dist[j], g[t][j]);
    }
    return ans;
}

int main() {
    memset(g, 0x3f, sizeof g);
    int k; cin >> n >> k;
    for(int i = 1; i <= k; i ++) {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        g[x][y] = g[y][x] = z;
    }
    printf("%d %d", n - 1, prim());
    return 0;
}

# kruskal

四、1143. 联络员

题目

#include<bits/stdc++.h>
using namespace std;
const int N = 10010;

int n, m, res;
int p[N];

struct E{
    int a, b, w;
    bool operator < (E const &W) const{
        return w < W.w;
    }
}e[N];

int find(int x){
    if(p[x] != x) return p[x] = find(p[x]);/* 如果p[x]不是x的祖宗节点,继续找。*/
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) p[i] = i;
    
    int a, b, w, d;
    for(int i = 0; i < m; i ++){
        scanf("%d%d%d%d", &d, &a, &b, &w);
        e[i] = {a, b, w};
        if(d == 1) {//这里是强制选择的改动
            a = find(a), b = find(b);
            if(a != b) p[a] = b;
            res += w;//只要是强制选择,无论是否连通都要加上价值
        }
    }

    sort(e, e + m);

    for(int i = 0 ; i < m; i ++){
        int a = e[i].a, b = e[i].b, w = e[i].w;
        a = find(a), b = find(b);
        if(a != b){
            p[a] = b;
            res += w;
        }
    }
    cout << res;

    return 0;
}

五、1144. 连接格点

题目

这道题看完题目,第一时间想到的就是,哇,这边这么多,怎么储存?
然后再一看,哦,枚举就好了呀。
由于竖着的边是恒为1的,横着的边是恒为2的,所以只需要从头到尾扫一遍竖着的,然后从头到尾扫一遍横着的就好了。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, res;
int p[N];

int z(int x , int y){return (x - 1) * m + y;}

int fnd(int x){
    if(p[x] != x) return p[x] = fnd(p[x]);/* 如果p[x]不是x的祖宗节点,继续找。*/
    return p[x];
    
}

int main(){
    cin >> n >> m;
    for(int x = 1; x <= n; x ++) 
        for(int y = 1; y <= m; y ++)
            p[z(x , y)] = z(x , y);
    int x1, y1, x2, y2;
    //将相连的加入同一个联通块
    while( ~scanf("%d%d%d%d", &x1, &y1, &x2, &y2) ) {
        if(fnd(z(x1 , y1)) != fnd(z(x2 , y2))) p[fnd(z(x2 , y2))] = fnd(z(x1 , y1));
        //千千万不要忘记是将各自的祖宗进行父子相认!这一句卡了一天……
    }
    
    for(int x = 1; x < n; x ++)
        for(int y = 1; y <= m; y ++) 
            if(fnd(z(x , y)) != fnd(z(x + 1 , y)))
                p[fnd(z(x + 1 , y))] = fnd(z(x , y)), res += 1;
                
    for(int x = 1; x <= n; x ++)
        for(int y = 1; y < m; y ++)
            if(fnd(z(x , y)) != fnd(z(x , y + 1)))
                p[fnd(z(x , y + 1))] = fnd(z(x , y)), res += 2;
    
    cout << res;
    return 0;
}

六、P4047 [JSOI2010]部落划分

星期三
题目

基本思路:
先将边进行排序,然后从小往大选边,知道合成k个部落;
注意:重点来了
当部落数量从n(我们一开始将一个人看作是一个部落)个部落合成到只有k个部落的时候,
很容易想到下一条边就是答案要求的。
但是有一个问题,那么就是下一条边如果连接的是已知部落里面的两个人,那么这条边就不能代表两个部落之间的距离,那么即使他当前最小也没有意义。
所以应该是,当下一条边连接的是不同的两个部落里边的两个人时,下一条边才是正解。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int p[N], n, m, k, cnt, res;
int x[N], y[N];
struct E {
	int a, b; double w;
}e[N];
bool operator < (E a, E b) {
	return a.w < b.w;
}

int find(int x){
	if(x != p[x]) return p[x] = find(p[x]);
	return x;
}

void add(int a, int b) {
	int x1 = x[b] - x[a], y1 = y[b] - y[a];
	e[m].a = a, e[m].b = b;
	e[m ++].w = x1 * x1 + y1 * y1;
}

void kru(){
	for(int i = 0; i < m; i ++){
		int a = find(e[i].a), b = find(e[i].b);
		if(cnt <= k && a != b) {
			printf("%.2lf", sqrt(e[i].w));
			return ;
		}
		if(a != b) {
			p[a] = b;
			cnt --;
		}
	}
}

int main() {
	cin >> n >> k;
	cnt = n;
	for(int i = 1; i <= n; i ++ ) p[i] = i;
	for(int i = 1; i <= n; i ++) cin >> x[i] >> y[i];
	for(int i = 1; i <= n; i ++)
		for(int j = i + 1; j <= n; j ++) 
			add(i, j);
	sort(e, e + m);
	kru();
	return 0;
}
// n = 3, k = 2, cnt = 3, cnt == k;

——2021年03月21日(周日)

七、走廊泼水节(kruskal

题目
点开一看,发现自己在11个月前做过,自己却一点记忆也没有,好意外。

题目大意:给你一个最小生成树,让你扩充为满足条件的完全图。
大概思路:
开局输入边,要排序,保证每两个点之间的距离都是最小的。
依次枚举所有边,当前边一定是所连接着的两个联通块之间的最短距离。
于是加上(sum[a] * sum[b] - 1) * (w[i] + 1)``sum代表当前连通块的点数.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 7000;
typedef long long ll;
int p[N], sum[N], n, t;
ll ans;
struct E { int a, b, w; }e[N];
bool operator < (E a, E b) {return a.w < b.w;}
int fa(int x) { 
    if(x != p[x]) return p[x] = fa(p[x]); 
    return x; 
}
void kru() {
    for(int i = 1; i < n; i ++) {
        int u = fa(e[i].a), v = fa(e[i].b);
        ans += (ll)(sum[u] * sum[v] - 1) * (e[i].w + 1);
        p[u] = v;
        sum[v] += sum[u];
    }
}
int main() {
	cin >> t;
    while(t --) {
        cin >> n;
        for(int i = 1; i < n; i ++) 
            scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].w);
        sort(e, e + n);
        for(int i = 1; i <= n; i ++) 
            p[i] = i, sum[i] = 1;
        ans = 0; 
        kru();
        cout << ans << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值