题解 P5058 【[ZJOI2004]嗅探器】

· · 题解

2020.3.25更新排版及原题解数据范围,感谢@♗Wendigo♝)

  考虑缩点时的过程,设uv的祖先,我们通过找v为根的搜索子树是否能延伸到时间戳小于u的节点来判断u是否为割点。如果该子树不能延伸至u以上,则去掉u后该子树会与其余部分“失去联系”。

  由此我们这样想:如果我们以一个信息中心a为根开始搜索,找到一个非根的割点u;此时若对应的子树根v的时间戳小于等于b的时间戳,则说明b存在于v的子树内。

  这很显然,由于dfndfs序更新,若还没搜到b,则其dfn0;或者dfn不为0而小于v,则说明b在进入v以前已经被搜到了。

  那么如果把u断掉,v的整棵子树都会与根a失去联系,u就是所求的点之一。

  算法只对原Tarjan函数的判断条件略做了修改,因此效率得到了极大保留,时间复杂度O(n+m)

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <queue>
const int maxn(200010);
const int maxm = 500010;
using namespace std;
int top = 1, head[maxn], n, a, b;
struct node {
    int to, nxt;
} edge[maxm<<1];
void tarjan(int);
bool cut[maxn];
inline void insert(int u, int v) {
    edge[++top] = (node) {v, head[u]};
    head[u] = top;
}
int main() {
    scanf("%d", &n);
    int u, v; 
    scanf("%d %d", &u, &v);
    while (!(u==0 && v==0)) {
        insert(u, v), insert(v, u);
        scanf("%d %d", &u, &v);
    }
    scanf("%d %d", &a, &b);
    tarjan(a);
    for(int i = 1; i <= n; i++)
        if(cut[i]) {
            printf("%d", i);
            return 0;
        }
    puts("No solution");
    return 0;
}
int dfn[maxn], low[maxn], timer;
void tarjan(int u) {
    dfn[u] = low[u] = ++timer;
    for(int i = head[u]; i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
            if(low[v]>=dfn[u] && u!=a && dfn[b]>=dfn[v]) 
                cut[u] = 1;
        } else
            low[u] = min(low[u], dfn[v]);
    }
    return;
}