题目:
求一张无向图从1走到N的必经点
n<=2e5 m<=4e5
分析:
必经点意为:删除后整个图不连通,但不能直接求割点。因为删掉的那个点可以使图不连通,但不能保证1与n不连通。
所以要删的是在1到n路径上的点。也就是说,删掉的点能使1和n隔开。
法1:
tarjan判环的时候从1开始,并维护一个数组siz[v]:表示v这个点在搜索树中的子树里有没有n这个点,每一次回溯的时候将标记回传给u(u是v的father)。
在判割点的条件:low[v]<=dfn[u](u的子树不能到达比u高的点) 再加一个限制:siz[v]==1(v这个子树中有n)
为什么不是siz[u]==1?
可能出现u的非v子树中有n,去掉u后1和n仍相连。
法2:
判环的时候多加这一个条件:dfn[v]<=dfn[n](n比v后遍历到)
画图可知满足题意
#include<bits/stdc++.h> using namespace std; #define N 200005 #define ri register int int siz[N],low[N],nex[N<<2],head[N],to[N<<2],dfn[N],cut[N]; int Ti=0,tot=0,n,m,ans=0; int read() { int x=0,fl=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); } while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar(); return x*fl; } void add(int a,int b) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; to[++tot]=a; nex[tot]=head[b]; head[b]=tot; } void dfs(int u,int fa) { dfn[u]=low[u]=++Ti; if(u==n) siz[u]=1; bool flagg=0; for(ri i=head[u];i;i=nex[i]){ int v=to[i]; if(!dfn[v] && v!=fa){ dfs(v,u); low[u]=min(low[u],low[v]); siz[u]|=siz[v]; if(siz[v] && low[v]>=dfn[u]) flagg=1; //这里一定是siz[v] 为了保证一定是从v的子树传上来的标记 而不是从u的另一个子树传上来的标记 //因为这里判u是割点是根据子树v来判断的 //也就是说 割掉u之后 子树v和其它部分会分裂 而n刚好在子树v中 而不会和1在一个块中 //还有一种打法:dfn[v]<=dfn[n](也就是说n在v后遍历到) //1. n在v的子树中 2.n在u的另一个非v的子树里(因为n一定要提前遍历 dfn才有值 所以正确性可以保证) } else if(v!=fa) low[u]=min(low[u],dfn[v]); } if(u!=1 && flagg) ans++,cut[u]=1; } int main() { //freopen("home.in","r",stdin); //freopen("home.out","w",stdout); int T=read(); while(T--){ n=read(); m=read(); while(m--) add(read(),read()); dfs(1,0); printf("%d\n",ans); for(ri i=1;i<=n;++i) if(cut[i]) printf("%d ",i); printf("\n"); for(ri i=1;i<=n;++i) dfn[i]=nex[i]=to[i]=head[i]=low[i]=siz[i]=cut[i]=0; Ti=0; tot=0; ans=0; } } /* 1 10 11 1 10 10 6 6 7 7 8 8 5 5 3 3 9 9 4 4 2 6 2 8 7 */