栈里面的点,它在整个过程中始终是从根到当前点的一条链。
在所有访问过的点(红点和绿点)中,只有它们是可能扩展出未被访问的点的。
所以当一个点(设为u)退栈的时候,考虑假设以这个点为根,能遍历到的点组成的集合,只会有两种情况:
1. 这个点已经被访问过了(变成红色或者绿色)
2. 这个点(设为v)还是黑色的,但是一定存在另一个红点(设为x),能够在不走红边的情况下访问到它,即
一定存在一条路径 u=>x=>v;
给定无向图的点(N)和边(M),输出图中所有的桥(按小编号排序)。
N<=2000
注意:重边算一条
输入:
10 17
2 1
2 6
2 8
3 2
3 5
4 2
4 7
5 3
5 4
6 3
7 1
7 2
7 3
7 5
8 2
9 6
10 8
输出:
3
2 8
6 9
8 10
样例第一行两个数分别表示点数和边数,接下来M行表示M条边。输出第一行为桥的个数,接下来每行两个数表示桥。
这道题,暴力不说了,直接说正规算法
定理:一个边是割边当且仅当他是树边并且他的开始节点的时间戳小于其后继节点的子树的所能找到的最早的节点。
自己证明。(*^▽^*)=>(#`O′)=>(╯‵□′)╯︵┻━┻
#include<cstdio> #include<cstring> #include<algorithm> #define N 2000+10 using namespace std; int head[N],num; struct edge{ int next,to; }; struct data{ int x,y; }; data ss[2*N*(N-1)]; edge e[2*N*(N-1)]; void add(int from,int to) { e[++num].next=head[from]; e[num].to=to; head[from]=num; } int flag[N],dfn[N],low[N]; int bu[N],bv[N]; int tim=0,tot; int cnt=0; int cmp(const data & a,const data & b) { if(a.x<b.x)return 1; if(a.x>b.x)return 0; if(a.y<b.y)return 1; return 0; } void tarjan_CE(int u,int fa) { //vis[u]=1; tim++; low[u]=dfn[u]=tim; // int son=0; for(int i=head[u];i;i=e[i].next) { int v=e[i].to; //if(v==fa)continue; if(!dfn[v]) { // son++; tarjan_CE(v,u); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]) { ss[++cnt].x=u,ss[cnt].y=v; } //flag[u]=1; // if(u==root&&son>=2) // { // ss[++cnt].x=u,ss[cnt].y=v; // } } else if(v!=fa) { low[u]=min(low[u],dfn[v]); } } return; } int main() { // freopen("sss.in","r",stdin); // freopen("sss.out","w",stdout); int n,m; scanf("%d%d",&n,&m); int x,y; for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); // if(!book[x][y]and!book[y][x]) // { add(x,y); add(y,x); // book[x][y]=book[y][x]=1; // } } cnt=0; tarjan_CE(1,1); sort(ss+1,ss+cnt+1,cmp); // for(int i=1;i<=n;i++) // { // if(flag[i]==1)cnt++; // //printf("%d ",i); // } printf("%d ",cnt); for(int i=1;i<=cnt;i++) { //if(!book[ss[i].x][ss[i].y]&&!book[ss[i].y][ss[i].x]) printf("%d %d ",ss[i].x,ss[i].y); //book[ss[i].x][ss[i].y]= book[ss[i].y][ss[i].x]=1; } // fclose(stdout); return 0; }