Official

G - Typical Path Problem Editorial by mechanicalpenciI


この問題は、頂点 \(B\) から頂点 \(A\) へのパスと、頂点 \(B\) から頂点 \(C\) へのパスの組であって、頂点 \(B\) 以外頂点を共有しないようなものがとれるか?という問題と言い換えることができます。この問題はネットワークフロー問題として次のように解くことができます。

\(G\) をもとに次のような流量制限付きの \((2N+2)\) 頂点 \((N+2M+3)\) 辺の有向グラフ \(G'\)を作ることを考えます。

  • 頂点は \(s,t\) および \(x_1,x_2,\ldots,x_N\), \(y_1,y_2,\ldots,y_N\) からなる。
  • \(i=1,2,\ldots,N\) について、\(x_i\) から \(y_i\) へ向けて最大流量 \(1\) の辺を張る。
  • \(i=1,2,\ldots,M\) について、\(y_{U_i}\) から \(x_{V_i}\) および \(y_{V_i}\) から \(x_{U_i}\) へ向けて最大流量 \(1\) の辺を張る。
  • \(s\) から \(y_B\) へ向けて最大流量 \(2\) の辺を張る。
  • \(y_A\) から \(t\) および \(y_C\) から \(t\) へ向けて最大流量 \(1\) の辺を張る。

この時、\(s\) から \(t\) への最大流の大きさが \(2\) である事と問題文の条件をみたすパスが存在することが同値となります。
すなわち、\(G\) 上において問題文の条件をみたすパス \(A\to u_1\to\cdots\to u_k\to B\to v_1\to\cdots\to v_{k'}\to C\) が存在するとき、\(G'\) 上における\(2\) つのパス
\(s\to y_B\to x_{u_k}\to y_{u_k}\to x_{u_{k-1}}\to \cdots \to y_{u_1}\to x_A\to y_A\to t\) と、
\(s\to y_B\to x_{v_1}\to y_{v_1}\to x_{v_2}\to \cdots \to y_{v_{k'}}\to x_C\to y_C\to t\) に沿ったフローが存在することが対応します。
前者が存在する時、 \(G'\) の定義から後者のようなパスは必ず取ることができます。
一方、\(G'\) 上において \(s\) から \(t\) への最大流の大きさが \(2\) であるとき、\(s\to y_B\) 以外の辺の流量制限は \(1\) であることから、\(y_A\) および \(y_C\) から流れを遡っていくことで \(2\) つのパスに沿った流量 \(1\) のフローに分離することができます。この際、この \(2\) つのパスは \(s\), \(y_B\) および \(t\) 以外の頂点を共有することはありません。なぜなら、頂点 \(x_i\) についてはそこから出る辺についての流量制限の合計が、頂点 \(y_i\) \((i\neq B)\) についてはそこへ入る辺についての流量制限の合計が \(1\) であり、その頂点を \(2\) つ以上のフローが流れることができないからです。そしてその経路は \(G'\) の形状から \(s\to y_B\to x_{i_1}\to y_{i_1}\to x_{i_2}\to\cdots\to y_{i_k}\to x_A\to y_A\to t\) および \(s\to y_B\to x_{j_1}\to y_{j_1}\to x_{j_2}\to\cdots\to y_{j_{k'}}\to x_C\to y_C\to t\) の形をしており、ここから対応する \(G\) 上で問題文の条件をみたすパスを構成することができます。

よって、このネットワークフローの問題を解けば良いです。ネットワークフローはDinic法など適切な方法で解けば、最大流が高々定数 \((2)\) であることから \(O(N+M)\) で解くことができ、よって十分高速にこの問題を解くことができました。

c++ による実装例:

#include <bits/stdc++.h>
#include <atcoder/all>
using namespace std;
using namespace atcoder;

int main(void){
	int n,m,a,b,c,x,y;
	cin>>n>>m;
	mf_graph<int> g(2*n+2);
	cin>>a>>b>>c;
	g.add_edge(0, 2*b, 2);
	g.add_edge(2*a, 2*n+1, 1);
	g.add_edge(2*c, 2*n+1, 1);
	for(int i=0;i<n;i++)g.add_edge(2*i+1, 2*i+2, 1);
	for(int i=0;i<m;i++){
		cin>>x>>y;
		g.add_edge(2*x, 2*y-1, 1);
		g.add_edge(2*y, 2*x-1, 1);
	}
	if(g.flow(0,2*n+1,2)==2)cout<<"Yes"<<endl;
	else cout<<"No"<<endl;

	return 0;
}

posted:
last update: