把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;//检验
}
posted @ 2020-11-25 20:34  TheLostWeak  阅读(76)  评论(0编辑  收藏  举报