2022杭电多校第一场 Path
题意:给定一张 \(n\) 个点 \(m\) 条边的图,其中每条边有边权和是否为特殊边的标记。如果你走过的是特殊边,那么在你走过特殊边后的下一步,你可以选择到达任意一个点,如果这个点你原来是不能直接到达的,那么花费就是0,否则就是 \(w_i-K\) 其中 \(w_i\) 表示走过特殊边后的那条边的权值。问到达每个点的最小花费是多少。
知识点:分层图,最短路
首先我们考虑一个比较暴力的做法,建立两层分层图,其中下面一层表示通过普通边到的顶点,上面表示通过特殊边到达的顶点,那么可以从上边向下面所有不是直接相连的边连一条 \(0\) 的边,然后直接跑最短路即可,这样是 \(n^2\) 的,所以我们考虑优化。
注意到,我们在最短路的过程中,更新的过程总是越来越远的,也就是后出队的点的距离总是大于先出队的点的距离
换句话说,最短路的更新过程总是从近到远,先更新完成的总是近点,然后才是远点,这就导致了每次我们取出队列中一个点,当这个点的距离是更新完成时,这些点组成的序列总是递增的,
也就是说,假设我们刚刚经过了一条特殊边到达了点 \(u\),记作 \(dis[u][1]\),假设我利用这个点更新了一个非邻点 \(v\) 记作 \(dis[v][0]\),我们假设我们后续又用一个点 \(t\) 更新了点 \(v\),也就是
\(dis[v][0] = dis[u][1] + 0\)
\(dis[v][0] = dis[t][0/1] + w\)
但是根据我们上面得到的结论,我们可以知道,之后拿来更新的 \(dis[t][0/1] \ge dis[u][1]\) ,所以只要是从 \(dis[u][1]\) 更新的点,都是已经是更新完成了的,每个点只会被这样更新一次,我们只需要用一个集合维护这样的点即可。
这题的重点就是理解我上面说的结论,如果不太懂的可以去看看dijkstra球最短路是怎么推的,这里就不再赘述。
#include <bits/stdc++.h>
#define endl '\n'
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL,LL> PLL;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
const LL INF = 1e18;
const double eps = 1e-6;
const double PI = acos(-1);
inline int lowbit(int x) {return x & (-x);}
struct node {
int nex;
int val, ops;
};
struct edge {
LL dis;
int idx, ops;
bool operator < (const edge &B) const {
return dis > B.dis;
}
};
inline void solve() {
int n, m; cin >> n >> m;
int S, K; cin >> S >> K;
vector<node> g[n + 1];
while (m -- ) {
int u, v, w, ops;
cin >> u >> v >> w >> ops;
g[u].push_back({v, w, ops});
}
set<int> s;
for (int i = 1; i <= n; i ++ ) if (i != S) s.insert(i);
priority_queue<edge, vector<edge>> q;
vector<vector<LL>> dis(n + 1, vector<LL>(2, INF));
vector<vector<bool>> vis(n + 1, vector<bool>(2));
vector<bool> st(n + 1);
dis[S][0] = 0;
q.push({dis[S][0], S, 0});
LL last = 0;
while (!q.empty()) {
auto t = q.top(); q.pop();
int u = t.idx, ops = t.ops;
if (ops == 1) {
for (auto ite : g[u]) st[ite.nex] = true;
vector<int> tmp;
for (auto ite : s) {
if (st[ite]) continue;
dis[ite][0] = dis[u][1];
q.push({dis[ite][0], ite, 0});
tmp.push_back(ite);
}
for (auto ite : tmp) s.erase(ite);
for (auto ite : g[u]) st[ite.nex] = false;
} else s.erase(u);
if (vis[u][ops]) continue;
vis[u][ops] = true;
assert(dis[u][ops] >= last);
last = dis[u][ops];
for (auto ite : g[u]) {
int v = ite.nex, w = ite.val - (ops == 1 ? K : 0);
if (dis[v][ite.ops] > dis[u][ops] + w) {
dis[v][ite.ops] = dis[u][ops] + w;
q.push({dis[v][ite.ops], v, ite.ops});
}
}
}
for (int i = 1; i <= n; i ++ ) {
LL res = min(dis[i][0], dis[i][1]);
if (res == INF) cout << -1 << ' ';
else cout << res << ' ';
}
cout << endl;
}
signed main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
auto now = clock();
#endif
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(2);
int T; cin >> T;
while (T -- )
solve();
#ifdef DEBUG
cout << "============================" << endl;
cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
return 0;
}