知识点:最短路
原题面: Luogu
扯
讲课的时候随便找的题,一眼了,瞎写了一下挂了。
看了题解,学习了。
题意简述
给定一 \(n\) 个点,\(m\) 条边的有向图,边有边权。
有无限个人同时从 \(1\) 号节点出发,可按照任意路径行走。
对于每一个节点,存在一些限制节点,当所有的限制节点都有人进入后,人才可进入该节点。
求在上述限制下,\(1\rightarrow n\) 的最短路。
\(1\le n\le 3\times 10^3\),\(1\le m\le 7\times 10^4\)。
分析题意
带有限制的最短路问题。
设 \(arrive_i\) 表示在满足上述限制下,\(1\rightarrow i\) 的最短路。
\(in_i\) 表示节点 \(i\) 的所有限制被摧毁的最短时间。
\(dis_i\) 表示进入 \(i\) 的实际时间。
则显然有:
\[in_i = \max\limits_{j \text{为} i \text{的限制节点}}{dis_j}_{}
\]
\[dis_i = \max{(arrive_i, in_i)}
答案即为 \(dis_n\)。
发现一个点能用于更新其他点,前提是该点能被进入,即所有该点的限制节点均可到达,且该点的所有前驱均可到达。
考虑使用 Dijkstra 求得上述 \(arrive\) 与 \(in\)。
每次找到还未被标记的,所有限制节点均已被标记的,\(dis\) 最小的节点,将其标记。
可以证明不存在其他节点可以疏导该节点,则用它来疏导与其相连的节点的 \(arrive\),并更新以其为限制节点的节点的 \(in\)。
数据范围较小,不进行优化也可过,代码中使用了堆优化。
以下是一个容易想到,但是错误的拓扑排序做法。
先跑出以 \(1\) 为起点的最短路。
由于保证有解,发现节点的限制关系,形成了一张 DAG。
对这张 DAG 进行拓扑排序,将 没有入度的点 的 \(dis_i\) 赋值为最短路,之后使用上述转移进行更新。
获得了 10 分的好成绩,以下是一组 hack 数据:
4 3
1 2 1
1 3 5
2 4 1
0
0
1 2
0
如果按上述方法进行,会输出 2,原因是终点并没有被直接限制,在 DAG 中没有入度,会被直接赋值。
爆零小技巧
多造数据卡自己一定没有错。
代码实现
//知识点:最短路
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#define ll long long
#define pr std::pair
#define mp std::make_pair
const int kMaxn = 3e3 + 10;
const int kMaxm = 1e5 + 10;
const ll kInf = 1e15 + 2077;
//=============================================================
int n, m, s;
int edge_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
ll arrive[kMaxn], in[kMaxn], dis[kMaxn];
bool vis[kMaxn];
int into[kMaxn];
std :: vector <int> pro[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(ll &fir_, ll sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ edge_num] = v_, w[edge_num] = w_;
ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Dijkstra(int s_) {
std :: priority_queue <pr <ll, int> > q;
memset(vis, false, sizeof (vis));
memset(arrive, 63, sizeof (arrive));
memset(arrive, 63, sizeof (dis));
arrive[s_] = 0;
dis[s_] = 0;
q.push(mp(0, s_));
while (! q.empty()) {
int u_ = q.top().second;
q.pop();
if (vis[u_]) continue ;
vis[u_] = true;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (arrive[v_] > dis[u_] + 1ll * w_) {
arrive[v_] = dis[u_] + 1ll * w_;
dis[v_] = std :: max(arrive[v_], in[v_]);
if (! into[v_]) q.push(mp(- dis[v_], v_));
}
}
for (int j = 0, size = pro[u_].size(); j < size; ++ j) {
int v_ = pro[u_][j];
into[v_] --;
in[v_] = std :: max(in[v_], dis[u_]);
dis[v_] = std :: max(arrive[v_], in[v_]);
if (! into[v_]) {
q.push(mp(- dis[v_], v_));
}
}
}
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_);
}
for (int i = 1; i <= n; ++ i) {
int l = read();
for (int j = 1; j <= l; ++ j) {
int u_ = read();
pro[u_].push_back(i);
into[i] ++;
}
}
Dijkstra(1);
printf("%lld\n", dis[n]);
return 0;
}
作者@Luckyblock,转载请声明出处。