Luogu P4616 [COCI2017-2018#5] Pictionary

发布于 # algorithm

1 题意

在第 ii 天,如果 gcd(a,b)=mi+1\gcd(a,b) = m - i + 1,那么 a,ba,b 之间会建立一条边

给定 a,ba,b,求 a,ba,b 最早什么时候连通

多组询问,离线

1n,q105,1mn,1a,bn1 \leq n ,q \leq 10^5, 1 \leq m \leq n, 1 \leq a, b \leq n

n,m,q,a,bZn,m,q,a,b \in \mathbb{Z}

2 思路

首先,对于 gcd(a,b)=i\gcd(a,b) = i,当且仅当 a=ci,b=di,gcd(c,d)=1a = ci,b = di, \gcd( c, d ) = 1

而对于 gcd(i,ki)(kN\*)\gcd( i, ki ) ( k \in \mathbb{N}^{\*} ) 显然等于 ii

所以当第 ii 天时,所有 mi+1m - i + 1 的倍数(包括其本身)都可以连通

容易发现,如果只有单次询问,只需要并查集顺序维护即可

由于 O(i=1nni)=O(nlogn)O(\sum^n_{i=1} \frac{n}{i}) = O(n \log n),所以程序是不会超时的

但是对于多组询问,直接使用这个方法就不行了

考虑并查集等同于从 ii 开始给每个没有连通ii 的倍数建边

(这里的没有连通指 iiii 的倍数不在同一集合内)

那么可以按照这个思路建出来一棵树,将每条边的边权设为连接时的 ii

那么对于询问 a,ba,b,只需要求两者最短路径中的瓶颈即可(即最小值)

这就是个求 lca 的操作,随便什么板子都能做

3 Code

#include <cstdio>
#include <cstring>

#include <algorithm>

const int INF = 0x3f3f3f3f;
const int N = 2e5 + 1e4;
const int K = 25;

int n, m, q;
int fa[N][K], min[N][K], dep[N];

// Set Start
struct Set {
    int fa[N];
    Set(int n) {
        for( int i = 0; i <= n; i ++ ) {
            fa[i] = i;
        }
    }
    int& get_fa( int cur ) {
        if( fa[cur] == cur )
            return fa[cur];
        int &p = get_fa( fa[cur] );
        fa[cur] = p;
        return p;
    }
    inline int& operator[] ( int index ) {
        return this -> get_fa(index);
    }
};
// Set End

// Edge Start
struct edge {
    int to, val, next;
} e[ N << 1 ];
int ecnt, ehead[N];
inline void add_edge( int cur, int to, int val ) {
    ecnt ++;
    e[ecnt].to = to;
    e[ecnt].val = val;
    e[ecnt].next = ehead[cur];
    ehead[cur] = ecnt;
}
// Edge End

void dfs( int cur, int la ) {
    for( int k = 1; k < K; k ++ ) {
        if( fa[ fa[cur][ k - 1 ] ][ k - 1 ] == 0 )
            continue;
        fa[cur][k] = fa[ fa[cur][ k - 1 ] ][ k - 1 ];
        min[cur][k] = std::min( min[cur][k], std::min( min[cur][ k - 1 ], min[ fa[cur][ k - 1 ] ][ k - 1 ] ) );
    }
    for( int i = ehead[cur]; i; i = e[i].next ) {
        int to = e[i].to;
        if( to == fa[cur][0] )
            continue;
        fa[to][0] = cur;
        min[to][0] = e[i].val;
        dep[to] = dep[cur] + 1;
        dfs( to, cur );
    }
}

int query( int from, int to ) {
    int res = INF;
    if( dep[from] < dep[to] )
        std::swap( from, to );
    for( int k = K - 1; k >= 0; k -- ) {
        if( dep[ fa[from][k] ] >= dep[to] ) {
            res = std::min( res, min[from][k] );
            from = fa[from][k];
        }
    }
    if( from == to )
        return res;
    for( int k = K - 1; k >= 0; k -- ) {
        if( fa[from][k] != fa[to][k] ) {
            res = std::min( res, std::min( min[from][k], min[to][k] ) );
            from = fa[from][k];
            to = fa[to][k];
        }
    }
    return std::min( res, std::min( min[from][0], min[to][0] ) );
}

int main() {
#ifdef woshiluo
    freopen( "luogu.4616.in", "r", stdin );
    freopen( "luogu.4616.out", "w", stdout );
#endif
    scanf( "%d%d%d", &n, &m, &q );
    // Init
    Set set(n);
    for( int i = m; i >= 1; i -- ) {
        for( int j = i + i; j <= n; j += i ) {
            //int u = get_fa(i), v = get_fa(j);
            int u = set[i], v = set[j];
            if( u != v ) {
                set[u] = set[v];
                add_edge( i, j, i );
                add_edge( j, i, i );
            }
        }
    }
    // Processing
    memset( min, INF, sizeof(min) );
    // min[1][0] = 0;
    dep[1] = 1;
    dfs( 1, 0 );
    // Answer
    while( q -- ) {
        int u ,v;
        scanf( "%d%d", &u, &v );
        printf( "%d\n", m - query( u, v ) + 1 );
    }
}
Comment seems to stuck. Try to refresh?✨

Woshiluo's NoteBook

「Jump up HIGH!!」