https://www.luogu.com.cn/problem/P1656
题意:给一张联通的无向图,问是否存在边,去掉它就不是全联通了,输出边的端点
input
6 6
1 2
2 3
2 4
3 5
4 5
5 6
output
1 2
5 6
思路:
用tarjan处理出每个点的low和dfn,对于一条边(u->j),如果low[j] > dfn[u],表明j点除了通过u点(父亲),无法到达u及以前的点,把它去掉就行了.
但是因为是无向图,所以需要用sign[i]表示i边是否走过,有个技巧是,一条无向边的去和来,对应的是i和i^1.
代码
/*
belong[i]表示i在第几个连通分量里
dfn[i]表示i点在搜索时的时间戳
low[i]表示i或i的子树能回溯到的最早的点的dfn
*/
int n,m;
int h[N],e[M],ne[M],idx;
int low[N],dfn[N],stk[N],top,belong[N],times;
bool instk[N];
int scc,size[N]; //强连通分量的个数和大小
bool sign[M];
void add(int a,int b) {
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
struct node {
int x,y;
} ans[M];
int ww;
bool cmp(node a,node b) {
if(a.x != b.x)return a.x < b.x;
return a.y < b.y;
}
void tarjan(int u) {
low[u] = dfn[u] = ++times;
stk[top++] = u;
instk[u] = true;
for(int i=h[u]; ~i; i=ne[i]) {
if(!sign[i]) {
sign[i] = sign[i^1] = true; //标记是否走过
int j = e[i];
if(!dfn[j]) {
tarjan(j);
low[u]=min(low[u],low[j]);
if(dfn[u] < low[j]) { //核心
ans[ww].x = min(u,j);
ans[ww++].y = max(u,j);
}
} else if(instk[j])low[u] = min(low[u],dfn[j]);
}
}
if(low[u] == dfn[u]) {
scc++;
int v;
do {
v = stk[--top];
instk[v] = false;
belong[v] = scc;
size[scc]++;
} while(v != u);
}
}
void work() {
n=rd(),m=rd();
memset(h,-1,sizeof(h));
while(m--) {
int a=rd(),b=rd();
add(a,b);add(b,a);
}
memset(dfn,0,sizeof(dfn));
memset(instk,false,sizeof(instk));
times = top = scc = 0;
idx = 0;
ww = 0;
for(int i=1; i<=n; i++) {
if(!dfn[i])tarjan(i);
}
sort(ans,ans+ww,cmp);
for(int i=0; i<ww; i++) {
printf("%d %d
",ans[i].x,ans[i].y);
}
}
int main() {
freopen("in.txt","r",stdin);
work();
return 0;
}