道路航线
题目链接:ybtoj高效进阶 21186
题目大意
有 n 个点,然后会不断加入一些单向边或者双向边。
然后问你分别在加入第几条边之后,从 a 可以到 b,和从 b 可以到 a。
思路
其实有一个比较经典的做法就是二分加 bfs 判是否可以走到。
不过还有一个 O ( n + m ) O(n+m) O(n+m) 的算法就是记搜。
大概就是如果一条边从能到的点连向了不能到的点,那这个点就能到了,我们就可以从新到的点开始 bfs 那些不能到的点,由于所有点会从不到变成到,所以这里是 O ( n ) O(n) O(n),枚举边是 O ( m ) O(m) O(m),所以是 O ( n + m ) O(n+m) O(n+m)。
这里就不写代码了。
代码
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
struct node {
int to, nxt;
}e[6000005];
int n, m, a, b, le[3000005], KK;
int u[3000005], v[3000005], op[3000005];
bool in[3000005];
queue <int> q;
void add(int x, int y) {
e[++KK] = (node){
y, le[x]}; le[x] = KK;
}
bool ck(int m, int s, int t) {
KK = 0; memset(le, 0, sizeof(le));
memset(in, 0, sizeof(in));
while (!q.empty()) q.pop();
for (int i = 1; i <= m; i++) {
add(u[i], v[i]);
if (!op[i]) add(v[i], u[i]);
}
in[s] = 1; q.push(s);
while (!q.empty()) {
int now = q.front();
q.pop();
for (int i = le[now]; i; i = e[i].nxt)
if (!in[e[i].to]) {
in[e[i].to] = 1;
if (e[i].to == t) return 1;
q.push(e[i].to);
}
}
return 0;
}
int work(int s, int t) {
int l = 1, r = m, re = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (ck(mid, s, t)) re = mid, r = mid - 1;
else l = mid + 1;
}
return re;
}
int main() {
// freopen("road.in", "r", stdin);
// freopen("road.out", "w", stdout);
scanf("%d %d %d %d", &n, &m, &a, &b);
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &u[i], &v[i], &op[i]);
}
printf("%d %d", work(a, b), work(b, a));
return 0;
}