最近在刷题过程中遇到了几道很有意思的图论题目,都是关于最短路径及其变种问题的。今天就把P1576、P2951、P1807、P2419、P4306这五道题的解题思路和代码实现做个详细分享,希望能帮助到也在刷图论题的朋友们。
这道题给出n种货币和m种汇率转换关系,要求找到从货币A到货币B的最大兑换率路径。输入格式中,z表示转换损失百分比(例如z=1表示1%的损失)。
这实际上是一个最长路径问题,可以使用Dijkstra算法的变种来解决。关键在于:
cpp复制#include <bits/stdc++.h>
using namespace std;
typedef long double ld;
int n,m,x,y,z,A,B;
bool vis[2010];
vector<pair<int, ld>> g[2010];
ld dis[2010];
struct Node {
int data;
ld dis;
friend bool operator < (Node a, Node b) {
return a.dis < b.dis; // 大顶堆
}
};
void dijkstra(int s) {
priority_queue<Node> q;
dis[s] = 1.0;
q.push({s, 1.0});
while (!q.empty()) {
Node top = q.top();
q.pop();
int data = top.data;
if (vis[data]) continue;
vis[data] = 1;
for (int i = 0; i < g[data].size(); i++) {
int v = g[data][i].first;
ld w = g[data][i].second;
if (dis[data] * w > dis[v]) { // 松弛条件
dis[v] = dis[data] * w;
q.push({v, dis[v]});
}
}
}
}
注意:这里使用了long double来保证精度,因为多次乘法可能会导致精度损失。
给定n个牧场和m条双向路径,所有路径长度均为1。要求找出:
这是一个标准的单源最短路径问题,由于边权均为1,可以使用BFS或Dijkstra算法。这里选择Dijkstra实现。
cpp复制void dijkstra() {
priority_queue<Node> q;
dis[s] = 0;
q.push({s, 0});
while (!q.empty()) {
int data = q.top().data;
q.pop();
if (vis[data]) continue;
vis[data] = 1;
for (int i = 0; i < (int)(g[data].size()); i++) {
int nv = g[data][i].first;
if (dis[data] + 1 < dis[nv]) { // 松弛操作
dis[nv] = dis[data] + 1;
q.push({nv, dis[nv]});
}
}
}
}
给定n个顶点m条边的有向无环图(DAG),边权可能为负,求从1到n的最长路径。
由于是DAG,可以使用拓扑排序+动态规划的方法。这里展示SPFA算法的实现,因为:
cpp复制void spfa() {
for (int i = 0; i <= n; i++)
dis[i] = -0x3f3f3f3f; // 初始化为极小值
dis[1] = 0;
q.push(1);
vis[1] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = 0; i < g[u].size(); i++) {
int v = g[u][i].first, w = g[u][i].second;
if (dis[u] + w > dis[v]) { // 改为求最大值
dis[v] = dis[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
}
提示:对于DAG,拓扑排序+DP是更高效的解法,时间复杂度O(n+m)
给定n头牛和m个胜负关系,确定有多少头牛的排名可以唯一确定。即对于牛i,可以确定它和其他n-1头牛的相对胜负关系。
使用Floyd-Warshall算法计算传递闭包:
cpp复制for (int k=1;k<=n;k++){
for (int i=1;i<=n;i++){
for (int j=1;j<= n;j++){
dp[i][j] |= dp[i][k] & dp[k][j]; // 传递闭包
}
}
}
for (int i=1;i<=n;i++){
int sum = 0;
for (int j=1;j<=n;j++){
if (dp[i][j]||dp[j][i]){ // 有确定的胜负关系
sum++;
}
}
if (sum==n-1){ // 与所有其他牛都有确定关系
cnt++;
}
}
给定n个节点的有向图,统计所有点对(i,j)中i可达j的数量。
使用bitset优化Floyd算法:
cpp复制for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++)
if (dp[i][k]) // 如果i可达k
dp[i] |= dp[k]; // 合并k的可达集合
}
for (int i = 1; i <= n; i++)
cnt += dp[i].count(); // 统计所有1的个数
技巧:使用bitset可以将时间复杂度从O(n^3)降到O(n^3/w),其中w是字长(通常为64)
最短路径算法选择指南:
常见优化技巧:
易错点:
在实际刷题中,理解问题本质并选择合适的算法是关键。这五道题覆盖了最短路问题的几种典型变种,建议熟练掌握每种算法的适用场景和实现细节。