[kuangbin带你飞]专题四 最短路练习

本文深入探讨了图算法在解决各种复杂问题中的应用,包括最短路径、正权回路检测、负权回路检测等,通过具体实例讲解了SPFA算法、Kruskal算法等在不同场景下的实现与优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Til the Cows Come Home

题意为问 N N N 1 1 1 的最短距离,直接套模板求单源最短路就好

#include <cstdio>
#include <algorithm>
#include <queue>
#include <climits>

const int MAXN = 1000;
const int MAXM = 2000;

std::queue<int> q;

struct Edge {
    int to, next, w;
} edge[MAXM << 1 + 2];

int tot, n, m;
int pre[MAXN], dis[MAXN];

void addEdge(int u, int v, int w) {
    tot++;
    edge[tot].w = w;
    edge[tot].to = v;
    edge[tot].next = pre[u];
    pre[u] = tot;
}

void init() {
    for (int i = 1; i <= n; i++)
        dis[i] = INT_MAX;
    dis[1] = 0;
    q.push(1);
}

void spfa() {
    while (!q.empty()) {
        int tmp = q.front();    q.pop();
        for (int i = pre[tmp]; i; i = edge[i].next) {
            if (dis[edge[i].to] > dis[tmp] + edge[i].w) {
                dis[edge[i].to] = dis[tmp] + edge[i].w;
                q.push(edge[i].to);
            }
        }
    }
}

int main()
{
    scanf("%d %d", &m, &n);
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        addEdge(u, v, w);
        addEdge(v, u, w);
    }
    init();
    spfa();
    printf("%d\n", dis[n]);
    return 0;
}

Frogger

题意是求两个青蛙之间最大路径的最小值,可以用Kruskal算法解决。
设某一条边为 x x x,当 x x x 加入后使得两个青蛙之间由不连通变为联通,那么 x x x 就为最小瓶颈边,证明的话,因为边是从小到大加的,所以很显然。。。

#include <cstdio>
#include <cmath>
#include <climits>
#include <algorithm>

const int MAXN = 200;
const int MAXM = 40000;

struct Union_Find {
	int dad[MAXN + 2];
	int n;

	void init() {
		for (int i = 1; i <= n; i++)
			dad[i] = i;
	}
	Union_Find(int n) {
		this->n = n;
		init();
	}
	int find(int x) {
		if (dad[x] != x) dad[x] = find(dad[x]);
		return dad[x];
	}
	void merge(int x, int y) {
		dad[find(x)] = find(y);
	}
	bool judge(int x, int y) {
		return find(x) == find(y);
	}
};

struct Point {
    int x, y;
} point[MAXN + 2];

struct Edge {
    int x, y;
    double w;

    bool operator < (const Edge &a) const {return w < a.w;}
} edge[MAXM + 2];

int n, cnt, turn;
double ans;

void init() {
    cnt = 0;    ans = INT_MAX;
    for (int i = 1; i <= n; i++)
        for (int j = i + 1; j <= n; j++) {
            double dis = sqrt((point[i].x - point[j].x) * (point[i].x - point[j].x) + (point[i].y - point[j].y) * (point[i].y - point[j].y));
            cnt++;
            edge[cnt].x = i, edge[cnt].y = j, edge[cnt].w = dis;
        }
}

int main()
{
    while (scanf("%d", &n) != EOF) {
        if (n == 0) return 0;
        turn++;
        for (int i = 1; i <= n; i++) {
            scanf("%d %d", &point[i].x, &point[i].y);
        }
        init();
        Union_Find bcj(n);
        std::sort(edge + 1, edge + 1 + cnt);
        for (int i = 1; i <= cnt; i++) {
            bcj.merge(edge[i].x, edge[i].y);
            if (bcj.judge(1, 2)) {
                ans = edge[i].w;
                break;
            }
        }
        printf("Scenario #%d\nFrog Distance = %.3f\n\n", turn, ans);
    }
    return 0;
}

Heavy Transportation

这个题意和上面那道题刚好反过来,是求最小路径的最大值,所以从大到小枚举加边,用Kruskal跑一下就好了

#include <cstdio>
#include <algorithm>

const int MAXN = 1000;

struct Edge {
    int x, y, w;

    bool operator < (const Edge &a) const {return w > a.w;}
} edge[1000000 + 2];

struct Union_Find {
	int dad[MAXN];
	int n;

	void init() {
		for (int i = 1; i <= n; i++)
			dad[i] = i;
	}
	Union_Find(int n) {
		this->n = n;
		init();
	}
	int find(int x) {
		if (dad[x] != x) dad[x] = find(dad[x]);
		return dad[x];
	}
	void merge(int x, int y) {
		dad[find(x)] = find(y);
	}
	bool judge(int x, int y) {
		return find(x) == find(y);
	}
};

int T, n, m, ans, rnd;

int main()
{
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= m; i++) {
            scanf("%d %d %d", &edge[i].x, &edge[i].y, &edge[i].w);
        }
        std::sort(edge + 1, edge + 1 + m);
        Union_Find b(n);
        for (int i = 1; i <= m; i++) {
            b.merge(edge[i].x, edge[i].y);
            if (b.judge(1, n)) {
                ans = edge[i].w;
                break;
            }
        }
        printf("Scenario #%d:\n%d\n\n", ++rnd, ans);
    }
    return 0;
}

Silver Cow Party

题意是给了 N N N 头牛,每头牛都要到达一个目标牛那里然后返回,求每头牛的最短路径的最大值。
从目标牛处返回很好计算,只要以目标牛为源点跑一边单源最短路即可,然后从每头牛到达目标牛的计算方法是把边取反,再以目标牛为源点跑一边单源最短路,然后把每头牛的两个 d i s dis dis 值加起来求一个最大值即可。

#include <cstdio>
#include <climits>
#include <queue>
#include <algorithm>

const int MAXN = 1000 + 2;
const int MAXM = 100000 + 2;

std::queue<int> q;

struct Edge {
    int to, w, next;
} edge[MAXM], edge2[MAXM];

int n, m, x, tot, tot2, ans;
int pre[MAXN], pre2[MAXN], dis[MAXN], dis2[MAXN];
bool in[MAXN], in2[MAXN];

void addEdge(int u, int v, int w) {
    tot++;
    edge[tot].to = v;
    edge[tot].w = w;
    edge[tot].next = pre[u];
    pre[u] = tot;
}

void addEdge2(int u, int v, int w) {
    tot2++;
    edge2[tot2].to = v;
    edge2[tot2].w = w;
    edge2[tot2].next = pre2[u];
    pre2[u] = tot2;
}

void spfa(int s) {
    for (int i = 1; i <= n; i++)
        dis[i] = INT_MAX;
    while(!q.empty()) q.pop();
    dis[s] = 0;
    q.push(s);

    while(!q.empty()) {
        int tmp = q.front();    q.pop();    in[tmp] = false;
        for (int i = pre[tmp]; i; i = edge[i].next) {
            if (dis[edge[i].to] > dis[tmp] + edge[i].w) {
                dis[edge[i].to] = dis[tmp] + edge[i].w;
                if (!in[edge[i].to]) {
                    q.push(edge[i].to);
                    in[edge[i].to] = true;
                }
            }
        }
    }
}

void spfa2(int s) {
    for (int i = 1; i <= n; i++)
        dis2[i] = INT_MAX;
    while(!q.empty()) q.pop();
    dis2[s] = 0;
    q.push(s);

    while(!q.empty()) {
        int tmp = q.front();    q.pop();    in2[tmp] = false;
        for (int i = pre2[tmp]; i; i = edge2[i].next) {
            if (dis2[edge2[i].to] > dis2[tmp] + edge2[i].w) {
                dis2[edge2[i].to] = dis2[tmp] + edge2[i].w;
                if (!in2[edge2[i].to]) {
                    q.push(edge2[i].to);
                    in2[edge2[i].to] = true;
                }
            }
        }
    }
}

int main()
{
    scanf("%d %d %d", &n, &m, &x);
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        addEdge(u, v, w);
        addEdge2(v, u, w);
    }
    spfa(x);    spfa2(x);
    for (int i = 1; i <= n; i++)
        ans = std::max(ans, dis[i] + dis2[i]);
    printf("%d\n", ans);
    return 0;
}

Currency Exchange

找正权回路,用 spfa,当一个点重复入队超过 n n n 次时,即存在正权回路。(需保证在队列里的点不能再次入队,即队列中不能同时存在多个同一个点)

#include <cstdio>
#include <algorithm>
#include <queue>
#include <climits>

const int MAXN = 100 + 2;

std::queue<int> q;

struct Edge {
    int to, next;
    double r, c;
} edge[MAXN << 1];

int n, m, s, tot;
int pre[MAXN], num[MAXN];
double v;
double dis[MAXN];
bool in[MAXN];

void addEdge(int u, int v, double r, double c) {
    tot++;
    edge[tot].to = v;
    edge[tot].r = r;
    edge[tot].c = c;
    edge[tot].next = pre[u];
    pre[u] = tot;
}

bool spfa() {
    while (!q.empty()) q.pop();

    dis[s] = v;
    q.push(s);
    in[s] = true;
    num[s]++;

    while (!q.empty()) {
        int tmp = q.front();    q.pop();
        for (int i = pre[tmp]; i; i = edge[i].next) {
            if (dis[edge[i].to] < (dis[tmp] - edge[i].c) * edge[i].r) {
                dis[edge[i].to] = (dis[tmp] - edge[i].c) * edge[i].r;
                if (!in[edge[i].to]) {
                    in[edge[i].to];
                    q.push(edge[i].to);
                    num[edge[i].to]++;
                    if (num[edge[i].to] > n) return true;
                }
            }
        }
    }
    if (dis[s] > v) return true;
    return false;
}

int main()
{
    scanf("%d %d %d %lf", &n, &m, &s, &v);
    for (int i = 1; i <= m; i++) {
        int u, v;
        double r1, c1, r2, c2;
        scanf("%d %d %lf %lf %lf %lf", &u, &v, &r1, &c1, &r2, &c2);
        addEdge(u, v, r1, c1);
        addEdge(v, u, r2, c2);
    }
    if (spfa()) puts("YES");
    else puts("NO");
}

Wormholes

虫洞可以回到过去,相当于负权值,所以找一下存不存在负权回路即可。

#include <cstdio>
#include <cstring>
#include <queue>
#include <climits>
#include <algorithm>

const int MAXN = 50000 + 2;
const int MAXM = 25000 + 2;

struct Edge {
    int to, w, next;
} edge[MAXM << 1];

int n, m, w, T, tot;
int dis[MAXN], pre[MAXN], num[MAXN];
bool in[MAXN];

void init() {
    tot = 0;
    memset(pre, 0, sizeof(pre));
}

void addEdge(int u, int v, int w) {
    tot++;
    edge[tot].to = v;
    edge[tot].w = w;
    edge[tot].next = pre[u];
    pre[u] = tot;
}

bool spfa(int s) {
    std::queue<int> q;
    memset(num, 0, sizeof(num));
    memset(in, 0, sizeof(in));
    for (int i = 1; i <= n; i++)
        dis[i] = INT_MAX;
    dis[s] = 0;
    in[s] = true;
    num[s]++;
    q.push(s);

    while (!q.empty()) {
        int tmp = q.front();    q.pop();    in[tmp] = false;
        for (int i = pre[tmp]; i; i = edge[i].next) {
            if (dis[edge[i].to] > dis[tmp] + edge[i].w) {
                dis[edge[i].to] = dis[tmp] + edge[i].w;
                if (!in[edge[i].to]) {
                    in[edge[i].to] = true;
                    num[edge[i].to]++;
                    if (num[edge[i].to] > n) return true;
                    q.push(edge[i].to);
                }
            }
        }
    }
    return false;
}

int main()
{
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d %d", &n, &m, &w);
        init();
        for (int i = 1; i <= m; i++) {
            int u, v, w;
            scanf("%d %d %d", &u, &v, &w);
            addEdge(u, v, w);
            addEdge(v, u, w);
        }
        for (int i = 1; i <= w; i++) {
            int u, v, w;
            scanf("%d %d %d", &u, &v, &w);
            addEdge(u, v, -w);
        }
        spfa(1) ? puts("YES") : puts("NO");
    }
    return 0;
}

MPI Maelstrom

题意就是给你 n n n 个点,求从 1 1 1 到其他 n − 1 n-1 n1 个点之间最短路的最大值,直接套模板就好,需要注意的就是可以用 a t o i ( ) atoi() atoi() 函数来把字符串转化为数字,很方便

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <queue>

const int MAXN = 100 + 5;

struct Edge {
	int w, to, next;
} edge[MAXN * MAXN];

int n, tot, ans = INT_MIN;
int dis[MAXN], pre[MAXN];

void addEdge(int u, int v, int w) {
	tot++;
	edge[tot].w = w;
	edge[tot].to = v;
	edge[tot].next = pre[u];
	pre[u] = tot;
}

void spfa() {
	for (int i = 1; i <=  n; i++)
		dis[i] = INT_MAX;
	dis[1] = 0;

	std::queue<int> q;
	q.push(1);

	while(!q.empty()) {
		int tmp = q.front();	q.pop();
		for (int i = pre[tmp]; i; i = edge[i].next) {
			if (dis[edge[i].to] > dis[tmp] + edge[i].w) {
				dis[edge[i].to] = dis[tmp] + edge[i].w;
				q.push(edge[i].to);
			}
		}
	}
}

int main()
{
	scanf("%d", &n);
	for (int i = 2; i <= n; i++) 
		for (int j = 1; j < i; j++) {
			char s[10];
			scanf("%s", s);
			if (s[0] != 'x') {
				addEdge(i, j, atoi(s));
				addEdge(j, i, atoi(s));
			}
		}
	spfa();
	for (int i = 2; i <= n; i++) 
		ans = std::max(ans, dis[i]);

	printf("%d\n", ans);
	return 0;
}

Cow Contest

题意为给你一些牛之间的 r a n k rank rank 关系,问最多能确定多少头牛的 r a n k rank rank
当一头牛有 n − 1 n-1 n1 r a n k rank rank 关系时,这头牛的 r a n k rank rank 是可以确定的,所以只要确定每头牛与其他牛之间的 r a n k rank rank 关系就行,如果把这种关系看成有向边的话,就可以转化到用 f l o y d floyd floyd 判断两点间是否联通

#include <cstdio>

const int MAXN = 100 + 2;

int n, m, ans;
int mp[MAXN][MAXN];

void floyd() {
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++) {
                if (!mp[i][j]) mp[i][j] = mp[i][k] && mp[k][j];
            }
}

int main()
{
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        mp[u][v] = 1;
    }
    floyd();
    for (int i = 1; i <= n; i++) {
        int sum = 0;
        for (int j = 1; j <= n; j++) {
            if (i == j) continue;
            if (mp[i][j] || mp[j][i]) sum++;
        }
        if (sum == n - 1) ans++;
    }
    printf("%d\n", ans);
    return 0;
}

施工中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值