2022“杭电杯”中国大学生算法设计超级联赛(4) - Link with Running (最短路图,缩点,最长路)

原题链接

题意

给定一个 n n n 个点, m m m 条边的图,每条边有 e e e, p p p, 两个权值。请找出一条从 1 1 1 n n n 的最短路,在满足路径上 e e e 的和最小的前提下,使得 p p p 的和最大。输出 m i n e min_e mine m a x p max_p maxp

数据范围

2 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 3 × 1 0 5 2\leq n\leq10^5,1\leq m\leq3\times10^5 2n105,1m3×105
1 ≤ u i , v i ≤ n , 0 ≤ e i , p i ≤ 1 0 9 1\leq u_i,v_i\leq n,0\leq e_i,p_i\leq10^9 1ui,vin,0ei,pi109

错误做法

首先说一个典型错误做法:我们要求满足最短路条件下的 p p p 的和最大,那么考虑在在更新最短路的时候直接维护一个存最大值的数组。 d i s t dist dist 数组维护最短路有, d p dp dp 数组维护最短路上的和的最大值,核心代码如下:

void dijkstra() {
   memset(dist, 0x3f, sizeof dist);
   memset(st, 0, sizeof st);
   dist[1] = 0;
   priority_queue<PII, vector<PII>, greater<PII>> q;
   q.push({0, 1});
   while (q.size()) {
   	PII tt = q.top();
   	int d = tt.first;
   	int v = tt.second;
   	q.pop();
   	if (st[v]) continue;
   	st[v] = 1;
   	for (int i = 0; i < g[v].size(); i ++) {
   		node tmp = g[v][i];
   		int to = tmp.to, e = tmp.e, p = tmp.p;

   		if (dist[to] > dist[v] + e) {
   			dp[to] = max(dp[to], dp[v] + p);
   			dist[to] = dist[v] + e;
   			q.push({dist[to], to});
   		} else if (dist[to] == dist[v] + e) {
   			dp[to] = max(dp[to], dp[v] + p);
   		}
   	}
   }
}

错误原因: d i j k s t r a dijkstra dijkstra 算法不能求最长路,也不能把边权转换为负数求最短路。这两者等价,不满足该算法的贪心的性质。具体证明可以看这篇博客:经典Dijkstra与最长路

题解

首先在图上以 e e e 为边权跑一边最短路,得到一张最短路图。枚举所有边,只要满足 d i s t [ v ] = d i s t [ u ] + e dist[v] = dist[u]+e dist[v]=dist[u]+e,即起始点到 u u u 的最短距离加上 e e e 等于 起始点到 v v v 的最短距离,则该边一定属于某条最短路,也一定属于最短路图。
建好最短路图后,考虑在图上求 p p p 的最长路。
我们可以发现, e i e_i ei p i p_i pi 可以等于 0 0 0。当存在一个环,环上所有的边权都是 0 0 0,该环被称为零环。当图内存在环时,我们考虑使用 t a r j a n tarjan tarjan 缩点,得到 D A G DAG DAG 后,再在图上使用拓扑排序来求最长路。

代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
const int N = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f;
typedef pair<int, int> PII;

struct node {
   int to, e, p;
};
int n, m, dist[N], st[N], ok[N];
vector<vector<node>> g(N), g2(N);
vector<vector<PII>> G(N);

int dfn[N], low[N], timestamp; 
int id[N], scc_cnt, _size[N], in_stk[N], in[N];
stack<int> stk;
int res, dp[N];

void init(int n) {
   for (int i = 1; i <= n; i ++) {
   	g[i].clear(); 
   	g2[i].clear();
   	G[i].clear();
   	ok[i] = id[i] = dfn[i] = low[i] = _size[i] = in_stk[i] = 0;
   	in[i] = dp[i] = 0;
   }
   timestamp = 0;
   scc_cnt = 0;
   while (stk.size()) stk.pop();
   res = 0;
}

void dijkstra() {
   memset(dist, 0x3f, sizeof dist);
   memset(st, 0, sizeof st);
   dist[1] = 0;
   priority_queue<PII, vector<PII>, greater<PII>> q;
   q.push({0, 1});
   while (q.size()) {
   	PII tt = q.top();
   	int d = tt.first;
   	int v = tt.second;
   	q.pop();
   	if (st[v]) continue;
   	st[v] = 1;
   	for (int i = 0; i < g[v].size(); i ++) {
   		node tmp = g[v][i];
   		int to = tmp.to, e = tmp.e, p = tmp.p;
   		if (dist[to] > dist[v] + e) {
   			dist[to] = dist[v] + e;
   			q.push({dist[to], to});
   		}
   	}
   }
}

void tarjan(int u) {
   dfn[u] = low[u] = ++ timestamp;
   stk.push(u); in_stk[u] = true;
   for (int i = 0; i < g2[u].size(); i ++) {
   	node tmp = g2[u][i];
   	int to = tmp.to, e = tmp.e, p = tmp.p;
   	if (!dfn[to]) {
   		tarjan(to);
   		low[u] = min(low[u], low[to]);
   	} else if (in_stk[to]) low[u] = min(low[u], low[to]);
   }
   if (dfn[u] == low[u]) {
   	++ scc_cnt;
   	int y;
   	do {
   		y = stk.top();
   		in_stk[y] = false;
   		stk.pop();
   		id[y] = scc_cnt;
   		_size[scc_cnt] ++;
   	} while (y != u);
   }
}

void topsort() {
   dp[id[1]] = 0;
   queue<int> q;
   for (int i = 1; i <= scc_cnt; i ++) 
   	if (!in[i])
   		q.push(i);
   while (q.size()) {
   	int u = q.front(); 
   	q.pop();
   	for (int i = 0; i < G[u].size(); i ++) {
   		int to = G[u][i].first, p = G[u][i].second;
   		dp[to] = max(dp[to], dp[u] + p);
   		in[to] --;
   		if (!in[to]) 
   			q.push(to);
   	}
   }
}


void slv() {
   cin >> n >> m;
   init(n);
   while (m --) {
   	int u, v, e, p;
   	cin >> u >> v >> e >> p;
   	g[u].push_back({v, e, p});
   }
   dijkstra();
   
   for(int i = 1; i <= n; i ++) {
   	for (int j = 0; j < g[i].size(); j ++) {
   		node tmp = g[i][j];
   		int to = tmp.to, e = tmp.e, p = tmp.p;
   		if (dist[i] + e == dist[to]) {
   			g2[i].push_back({to, e, p});
   		}
   	}
   }
   for (int i = 1; i <= n; i ++)
   	if (!dfn[i] && g2[i].size())
   		tarjan(i);

   for(int i = 1; i <= n; i ++) {
   	for (int j = 0; j < g2[i].size(); j ++) {
   		node tmp = g2[i][j];
   		int to = tmp.to, e = tmp.e, p = tmp.p;
   		if (id[i] != id[to]) {
   			G[id[i]].push_back({id[to], p});
   			in[id[to]] ++; 
   		} 
   	}
   }
   topsort();
   
   cout << dist[n] << ' ' << dp[id[n]] << '\n';
}
signed main() {
   cin.tie(0)->sync_with_stdio(0);
   int _; cin >> _;
   while (_--)
   	slv();
} 	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值