「LOJ3406」Tom & Jerry
题目
点这里看题目。
给定一张包含 \(n\) 个顶点和 \(m\) 条边的 无向连通图,Tom 和 Jerry 在图上进行了 \(q\) 次追逐游戏。
在第 \(i\) 次游戏中,Tom 一开始位于顶点 \(a_i\),而 Jerry 一开始位于顶点 \(b_i\)(双方任何时候都知道自己和对方的位置),追逐规则如下:
-
Jerry 和 Tom 交替行动,Jerry 先行动。
-
Jerry 每次行动可以通过无向图中的 任意多条边(可以选择不移动),但是在移动过程中不能经过 Tom 当前所在的结点,否则就会被抓住。
-
Tom 每次行动只能通过无向图中的 至多一条边(可以选择不移动)。
-
如果 Tom 在一次行动后到达了 Jerry 的位置,那么 Tom 胜利。
Tom 尽量想要胜利,而 Jerry 会尽量阻止 Tom 胜利。
现在你需要对于每一局游戏,求出 Tom 是否一定能在有限次行动内获胜。
所有数据满足:\(1\leq n,m,q\leq 10^5\),\(1\leq x,y,a,b\leq n\),\(a_i\ne b_i\),且给出的图连通、无自环、无重边。
分析
不妨考虑最简单的情况:整个图都是点双连通的。
这个局面对于 Jerry 非常有利,因为根据点双性质,Jerry 可以到达点双内任意一个点。但是,可以注意到如果存在一个结点和图中其它结点都相邻,则 Tom 可以站在这个结点上,而 Jerry 无论怎么跑下一步都会被抓;反之,若不存在这样的结点,则 Jerry 始终可以保持自己和 Tom 不相邻。于是,此时 Tom 存在必胜策略,当且仅当图中存在一个结点和其它结点都相邻。
自然地,对于一般图,我们可以想到将它分解为若干个点双连通分量,顺便建立一个圆方树的结构。对于一个点双连通的点的子集 \(C\subseteq V\),若其中一点 \(u\) 满足 \(\forall v\in C\setminus \{u\},(u,v)\in E\),则我们认为 \(u\) 在这个点双内显黑色,否则 \(u\) 就在这个点双内显白色。上面的分析就指出,如果整个图都点双连通,则 Tom 存在必胜策略当且仅当图中的点不全显白色。
将这个条件再拓展一下,如果一个点双内的点在这个点双内全显白色,则称这个点双为“纯白”。容易发现,如果 Jerry 可以到达一个“纯白”的点双,则 Jerry 就赢麻了。这首先要求图中存在一个“纯白”点双(obviously),其次要求“Jerry 可以到达它”。
若图中存在“纯白”点双,对可达性进行讨论:
-
如果 Tom 一开始站在一个非割点 \(b\) 上,则 Jerry 显然可以到达“纯白”的点双。
-
如果 Tom 一开始恰好站在割点 \(b\) 上,则我们大可忽略图上 Jerry 不能到达的点,并将圆方树的根定在 \(b\)。
假设某一时刻 Tom 在点 \(x\)。因为我们时刻忽略了 Jerry 不能到达的点,所以 \(x\) 目前只会在一个点双中。如果 \(x\) 在这个点双中显白色,则 Jerry 可以呆在该点双内某个与 \(x\) 不相邻的点,等 Tom 移动时逃出去;否则,Jerry 不能留在这个点双内,而 Tom 则可以向 Jerry 移动一条边,原先的这个点双中的点就会被“忽略”。
称圆方树上一个点双内最浅的点为该点双的根。则容易将上面的分析推广并得出,如果以 \(b\) 为根时,Jerry 能够到的点双中,存在一个点双满足根在其中显白色,则 Jerry 可以到达原图中的任意一点,否则 Jerry 一定会被抓住。如果 Jerry 能够到达原图中任意一点,那它自然就能到达图中存在的“纯白”点双了。
于是,图中存在“纯白”点双的情况就讨论完了。如果不存在,Jerry 还能够赢吗?
仍然考虑根在点双内显白色的点双。当 Tom 进入了这个点双且不在点双的根上时,我们将圆方树的根重新定在 Tom 所在的结点。此时,Jerry 又可以离开这个点双(它肯定不能留在原来的点双内),而如果此时还存在另一个点双,它的根在点双内显白色,则 Jerry 可以跑到那个点双中,重复勾引 Tom 的过程。这个过程在 Tom 进入新点双时仍可以重复——Jerry 可以回到旧点双。于是 Jerry 再次赢麻。
这个感性分析可以归纳为如下结论:对于两个不同的点双 \(C_1,C_2\),若存在 \(u\in C_1,v\in C_2\) 满足以 \(u\) 为根时 \(C_2\) 的根为 \(v\) 且 \(v\) 在 \(C_2\) 中显白色,以 \(v\) 为根时 \(C_1\) 的根为 \(u\) 且 \(u\) 在 \(C_1\) 中显白色,且 Jerry 可以到达 \(C_1\) 或 \(C_2\) 中任一一个,则 Jerry 可以赢。直观意义下,相当于是 \(u,v\) 可以不经过 \(C_1\cup C_2-\{u\}-\{v\}\) 的点互达,我们把 \((C_1,C_2,u,v)\) 称为一条“赢麻”路好了。
前面可达性的讨论仍然成立——只需要注意到以 \(b\) 为根时,如果“赢麻”路 \((C_1,C_2,u,v)\) 的端点 \(u,v\) 存在一个可以被 Jerry 到达,则 \(u,v\) 之中必然存在一个点,满足其为 \(C_1\)(或 \(C_2\))的根且在 \(C_1\)(或 \(C_2\))中显白色且 Jerry 可以到达,而 Jerry 只需要一开始就“蹲守”在那个点双中即可。
反之,如果上面条件都不成立,即 Tom 一开始站在割点 \(b\) 上,且:
-
以 \(b\) 为根时,Jerry 能够到达的点双的根都在点双内显黑色。
-
或者图中既不存在“纯白”点双,又不存在“赢麻”路。
时,Jerry 一定会被抓住。延续上面分析的精神,则这一点不难证明,故略去。
实现时,我们首先要处理出图中是否存在“纯白”点双和“赢麻”路,这个可以在圆方树上 DFS 一遍得到;其次要处理出子树内是否存在根显白色的点双,这个可以用换根 DP 得到。最后询问时还需要额外查询 K 级祖先,预处理倍增数组即可。
复杂度为 \(O(m+(n+q)\log n)\)。
代码
#include <bits/stdc++.h>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
typedef long long LL;
const int MAXN = 2e5 + 5, MAXLOG = 19;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
struct MyGraph {
struct Edge {
int to, nxt;
} Graph[MAXN << 1];
int head[MAXN], cnt;
MyGraph(): head{}, cnt( 1 ) {}
inline const Edge& operator [] ( const int &idx ) const { return Graph[idx]; }
inline void AddE( const int &from, const int &to ) {
AddEdge( from, to ), AddEdge( to, from );
}
inline void AddEdge( const int &from, const int &to ) {
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}
};
MyGraph org, tre;
std :: vector<int> compo[MAXN], edg[MAXN];
std :: vector<bool> whit[MAXN];
int deg[MAXN];
int ance[MAXN][MAXLOG], dep[MAXN];
int upWhit[MAXN], dnWhit[MAXN];
int ord[MAXN], siz[MAXN];
bool anyPath, anyPure;
int outTim[MAXN];
int stk[MAXN], top;
int DFN[MAXN], LOW[MAXN], ID = 0;
bool isCut[MAXN];
int edgFr[MAXN], edgTo[MAXN];
int N, M, Q, ntot, lg2;
void Tarjan( const int &u, const int &fa ) {
int sonCnt = 0;
stk[++ top] = u;
DFN[u] = LOW[u] = ++ ID;
for( int i = org.head[u], v ; i ; i = org[i].nxt )
if( ! DFN[v = org[i].to] ) {
Tarjan( v, u ), sonCnt ++;
LOW[u] = std :: min( LOW[u], LOW[v] );
if( LOW[v] >= DFN[u] ) {
isCut[u] = true;
compo[++ ntot].push_back( u );
do compo[outTim[stk[top]] = ntot].push_back( stk[top] );
while( stk[top --] ^ v );
for( const int &x : compo[ntot] )
tre.AddE( ntot, x );
}
} else if( v ^ fa )
LOW[u] = std :: min( LOW[u], DFN[v] );
if( ! fa && sonCnt == 1 ) isCut[u] = false;
}
inline int GetHandle( const int &id, const int &x ) {
return std :: lower_bound( compo[id].begin(), compo[id].end(), x ) - compo[id].begin();
}
void DFS1( const int &u, const int &fa ) {
siz[u] = 1, ord[u] = ++ ord[0];
dep[u] = dep[ance[u][0] = fa] + 1;
for( int i = tre.head[u], v ; i ; i = tre[i].nxt )
if( ( v = tre[i].to ) ^ fa )
DFS1( v, u ), siz[u] += siz[v];
if( u > N ) {
int n = compo[u].size();
rep( i, 0, n - 1 ) {
int v = compo[u][i];
if( v == fa ) dnWhit[u] += whit[u][i];
else {
anyPath |= whit[u][i] && dnWhit[v];
dnWhit[u] += dnWhit[v];
}
}
} else {
for( int i = tre.head[u], v ; i ; i = tre[i].nxt )
if( ( v = tre[i].to ) ^ fa ) {
anyPath |= dnWhit[u] && dnWhit[v];
dnWhit[u] += dnWhit[v];
}
}
}
void DFS2( const int &u, const int &fa ) {
for( int i = tre.head[u], v ; i ; i = tre[i].nxt )
if( ( v = tre[i].to ) ^ fa ) {
if( v > N )
upWhit[v] = upWhit[u] + dnWhit[u] - whit[v][GetHandle( v, u )];
else
upWhit[v] = upWhit[u] - dnWhit[v] + whit[u][GetHandle( u, v )];
DFS2( v, u );
}
}
inline int KthAnce( int u, int k ) {
for( ; k ; k &= k - 1 )
u = ance[u][__builtin_ctz( k )];
return u;
}
int main() {
Read( N ), Read( M ), Read( Q );
rep( i, 1, M ) {
Read( edgFr[i] ), Read( edgTo[i] );
org.AddE( edgFr[i], edgTo[i] );
}
ntot = N, Tarjan( 1, 0 );
outTim[1] = ntot;
rep( i, 1, M )
edg[std :: min( outTim[edgFr[i]], outTim[edgTo[i]] )].push_back( i );
rep( i, N + 1, ntot ) {
int n = compo[i].size(), m = edg[i].size();
std :: sort( compo[i].begin(), compo[i].end() );
rep( j, 0, n - 1 ) deg[compo[i][j]] = 0;
rep( j, 0, m - 1 )
deg[edgFr[edg[i][j]]] ++,
deg[edgTo[edg[i][j]]] ++;
whit[i].resize( n, false );
bool pure = true;
rep( j, 0, n - 1 )
pure &= ( whit[i][j] = deg[compo[i][j]] < n - 1 );
anyPure |= pure;
}
DFS1( 1, 0 );
DFS2( 1, 0 );
lg2 = log2( ntot );
rep( j, 1, lg2 ) rep( i, 1, ntot )
ance[i][j] = ance[ance[i][j - 1]][j - 1];
while( Q -- ) {
int a, b;
Read( a ), Read( b );
bool anyWhit = false;
if( ord[a] <= ord[b] && ord[b] < ord[a] + siz[a] )
anyWhit = dnWhit[KthAnce( b, dep[b] - dep[a] - 1 )];
else
anyWhit = upWhit[a];
anyWhit |= ! isCut[a];
puts( anyWhit && ( anyPath || anyPure ) ? "No" : "Yes" );
}
return 0;
}