「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;
}
posted @ 2023-06-14 16:52  crashed  阅读(83)  评论(0编辑  收藏  举报