边双缩点
例题:加油站 NKOJ3082
某省有N座城市,这些城市间通过M条双向道路连接起来,使得任意两个城市都可相互到达。
每个城市都有两种车,一种使用柴油,一种使用汽油。
知名企业家何老板在每座城市都开设了一个加油站,有的加油站只卖汽油,有的加油站只卖柴油,有的加油站两种油都卖。
最近下了很长一段时间的暴雨,道路有被山洪冲毁的危险。
何老板很担心,他想知道,哪些道路毁坏了会使得某个或某些城市的车辆无法加到合适的油。
请你计算这样的道路总共有多少条?(两个城市间最多有一条道路直接相连)
简单分析一下
将该图边双缩点后,在同一大点的两点一定可以互相到达,也就是说同一大点内的节点可以相互提供汽油和柴油。
我们看大点与大点间的关系。
由于缩了点,图变成了树,于是树上的边一旦删去肯定会把图分成若干个连通块,连通块内部可以互相满足,但不能向外提供服务或者得到服务,就很容易判断一条边能不能断。
#include<bits/stdc++.h> using namespace std; #define re register int const int N=2e5+5; int tt=1,las[N],ed[N],nt[N]; inline void add(int x,int y){ed[++tt]=y;nt[tt]=las[x];las[x]=tt;} int dfn[N],low[N],total; int bridge[N],scc,bel[N]; inline void tarjan(int x,int pre) { dfn[x]=low[x]=++total; for(re i=las[x];i;i=nt[i]) { int v=ed[i]; if(!dfn[v]) { tarjan(v,i); low[x]=min(low[x],low[v]); if(low[v]>dfn[x])bridge[i]=bridge[i^1]=1; } else if(i!=(pre^1)&&dfn[v]<dfn[x])low[x]=min(low[x],dfn[v]); } } int a[N],b[N],A[N],B[N]; inline void dfs(int x,int col) { bel[x]=col; A[col]|=a[x];B[col]|=b[x]; for(re i=las[x];i;i=nt[i]) if(!bridge[i]&&!bel[ed[i]])dfs(ed[i],col); } vector<int>G[N]; int sz1[N],sz2[N],totA,totB,dep[N]; inline void DFS(int x,int fa) { dep[x]=dep[fa]+1; sz1[x]=A[x];sz2[x]=B[x]; for(re v:G[x]) if(v!=fa) { DFS(v,x); sz1[x]+=sz1[v]; sz2[x]+=sz2[v]; } } signed main() { int n,m,a1,b1; scanf("%d%d%d%d",&n,&m,&a1,&b1); while(a1--){int x;scanf("%d",&x);a[x]=1;} while(b1--){int x;scanf("%d",&x);b[x]=1;} while(m--){int x,y;scanf("%d%d",&x,&y);add(x,y);add(y,x);} tarjan(1,-1); for(re i=1;i<=n;++i)if(!bel[i])scc++,dfs(i,scc); for(re i=2;i<=tt;i+=2) if(bridge[i]) { int x=ed[i],y=ed[i^1]; x=bel[x];y=bel[y]; G[x].push_back(y); G[y].push_back(x); } for(re i=1;i<=scc;++i)totA+=A[i],totB+=B[i]; DFS(1,0); int ans=0; for(re i=2;i<=tt;i+=2) if(bridge[i]) { int x=ed[i],y=ed[i^1]; x=bel[x];y=bel[y]; if(dep[x]>dep[y])swap(x,y); if(sz1[y]==0||sz2[y]==0){ans++;continue;}//y子树内挂掉 if(totA==sz1[y]||totB==sz2[y]){ans++;continue;}//y子树外挂掉 } printf("%d",ans); return 0; }