【CF555E】Case of Computer Network(边双缩点水题)
- 给定一张\(n\)个点\(m\)条边的无向图以及\(q\)个点对\((s,t)\)。
- 问是否存在一种给边定向的方案,使得每个\(s\)都能到达对应的\(t\)。
- \(n,m,q\le2\times10^5\)
边双缩点+树上差分
显然一个边双中一定存在一种定向方案使得任意两点都能互相到达。
而若一条路径经过了两个边双中间的一条桥,那么这条桥的方向就被确定了。
如果一条桥会被从两种方向经过,那么就产生了矛盾,说明无解。
考虑边双缩点之后得到的是森林,那么每棵树中的两点\(s,t\)就是先从\(s\)向上走到\(LCA\)再向下走回\(t\),也就是\(s\)到\(LCA\)的边都需要向上,\(t\)到\(LCA\)的边都需要向下。
那么只要树上差分一下,看看是否会有一条边被打上两种标记就好了。
代码:\(O(n+m+q)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define LN 20
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m;
namespace G
{
int ee,lnk[N+5];struct edge {int to,nxt,p;}e[2*N+5];
I void add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
int d,dfn[N+5],low[N+5];I void Tarjan(CI x,CI lst=0)//边双
{
dfn[x]=low[x]=++d;for(RI i=lnk[x],t=1;i;i=e[i].nxt)
{
if(t&&e[i].to==lst) {t=0;continue;}
if(dfn[e[i].to]) {Gmin(low[x],dfn[e[i].to]);continue;}
if(Tarjan(e[i].to,x),Gmin(low[x],low[e[i].to]),low[e[i].to]<=dfn[x]) continue;
e[i].p=e[((i-1)^1)+1].p=1;
};
}
int cnt,c[N+5];I void Col(CI x,CI C,CI lst=0)//缩点
{
c[x]=C;for(RI i=lnk[x];i;i=e[i].nxt)
e[i].to^lst&&!e[i].p&&!c[e[i].to]&&(Col(e[i].to,C,x),0);
}
}
namespace T//缩点后得到的森林
{
int ee,lnk[N+5];struct edge {int to,nxt;}e[2*N+5];
I void add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
int d[N+5],f[N+5][LN+5];I void Init(CI x)//初始化
{
RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];
for(i=lnk[x];i;i=e[i].nxt) e[i].to^f[x][0]&&
(d[e[i].to]=d[f[e[i].to][0]=x]+1,Init(e[i].to),0);
}
I int LCA(RI x,RI y)
{
RI i;for(d[x]<d[y]&&(x^=y^=x^=y),i=0;d[x]^d[y];++i) (d[x]^d[y])>>i&1&&(x=f[x][i]);
if(x==y) return x;for(i=LN;~i;--i) f[x][i]^f[y][i]&&(x=f[x][i],y=f[y][i]);return f[x][0];
}
int tg[N+5][2];I void U(CI x,CI y)//树上差分
{
RI z=LCA(x,y);z?(++tg[x][0],++tg[y][1],--tg[z][0],--tg[z][1]):(puts("No"),exit(0),0);//如果不连通直接无解
}
I void Check(CI x)//检验
{
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^f[x][0]&&
(Check(e[i].to),tg[x][0]+=tg[e[i].to][0],tg[x][1]+=tg[e[i].to][1]);
if(tg[x][0]&&tg[x][1]) puts("No"),exit(0);//如果一条边打上了两种标记就无解
}
}
int main()
{
using namespace G;RI Qt,i,x,y;scanf("%d%d%d",&n,&m,&Qt);
for(i=1;i<=m;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
for(i=1;i<=n;++i) !dfn[i]&&(Tarjan(i),0);for(i=1;i<=n;++i) !c[i]&&(Col(i,++cnt),0);//Tarjan+缩点
for(i=1;i<=ee;i+=2) e[i].p&&(T::add(c[e[i].to],c[e[i+1].to]),T::add(c[e[i+1].to],c[e[i].to]),0);//建新图
for(i=1;i<=cnt;++i) !T::d[i]&&(T::d[i]=1,T::Init(i),0);//初始化
W(Qt--) scanf("%d%d",&x,&y),T::U(c[x],c[y]);//将询问差分
for(i=1;i<=cnt;++i) T::d[i]==1&&(T::Check(i),0);return puts("Yes"),0;//检验
}
待到再迷茫时回头望,所有脚印会发出光芒