XVI Open Cup Grand Prix of Moscow-2016 K. Bipartite Graph

题目链接

XVI Open Cup Grand Prix of Moscow-2016 K. Bipartite Graph(需要权限号)

题目大意

一个二分图,左部点 \(0,1,...,n-1\),右部点 \(0,1,...,m-1\),开始连好了 \(k\) 条边 \((u_i,v_i)\)。接下来进行若干轮连边,每次加边 \((i\!\!\!\mod \!n,i\!\!\!\mod \!m)\),求至少经过多少轮后图会连通,无解输出 \(-1\)

\(1\leq n,m\leq 10^9\)\(0\leq k\leq 10^5\)

思路

考虑把点排成一列,先不妨设 \(n\geq m\),前 \(m\) 步所有右部点会与左部点连通,于是左部点自己和自己连边,\(m\) 轮后开始借助右部点错位连边,画一画会发现,有解时经过 \(n+m\) 轮二分图必然连通,像这样:

竖线两侧分别为左右部点,这样标号以后,容易发现这实际上即 \(i\)\((i+m)\!\!\!\mod (n+m)\) 连边。

\(b=n,\;a=n+m\),那么题目转化为有一张 \(b\) 个点的一般图,第 \(i\) 次加边 \((i,(i+b)\!\!\!\!\mod\! a)\),求使连通的最少轮数。

注意到在 \(a-b\) 步后,所有竖线右侧的点都与左侧点连通,此时模 \(b\) 同余的点组成一个连通块,可以发现原问题现在变成了一个大小为 \(b\),每次步幅 \(a\%b\) 的子问题,而 \(k\) 条预连边的两边端点编号对 \(b\) 取模,然后就可以递归解决了。

考虑递归边界,显然应在 \(b\leq 2k\) 时停止,否则在本轮辗转相除后图的大小 \(\leq 2k\),就有可能在之前某一步已经连通了,但是此时 \(a\) 可能很大,于是用辗转相减法继续缩小问题规模。这里是从左往右减少点的,所以序号的处理方式和直接递归略有不同。

最终的图满足 \(b\leq 2k,a\leq b+2k\),是 \(4e5\) 级别的,可以直接用并查集维护连通性,在最后 \(a\) 步中找到最先连通的那一轮,若没有则必然无解。

时间复杂度 \(O(k\log n+k\alpha(k))\)

Code

具体实现时注意最后 \(b=0\) 的情况,需特判。

数据比较水,实测递归边界直接写成 \(a+b<4e5\) 居然也能过。

#include<iostream>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 101000
using namespace std;

int n, m, k;
int u[N], v[N], fa[4*N];
int ans;

int find(int x){ return fa[x] == x ? x : fa[x] = find(fa[x]); }
bool merge(int a, int b){
    int x = find(a), y = find(b);
    if(x == y) return false;
    fa[x] = y; return true;
}

int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m>>k;
    rep(i,1,k) cin>>u[i]>>v[i], v[i] += n;
    if(n < m){
        rep(i,1,k) u[i] += m, v[i] -= n, swap(u[i], v[i]);
        swap(n, m);
    }
    int t = n; n += m, m = t;
    while(m > 2*k){
        if(m == 0){ cout<<"-1\n"; return 0; }
        ans += n-m, t = m, m = n%m, n = t;
        rep(i,1,k) u[i] %= n, v[i] %= n;
    }
    if(m == 0 && n > 2*k && n != 1){ cout<<"-1\n"; return 0; }
    int ex = m && n > m+2*k ? (n-m-2*k) / m : 0;
    ans += ex * m, n -= ex * m;
    rep(i,1,k){
        u[i] = u[i] < ex*m ? u[i]%m : u[i]-ex*m;
        v[i] = v[i] < ex*m ? v[i]%m : v[i]-ex*m;
    }
    
    rep(i,0,n-1) fa[i] = i;
    int comp = n;
    rep(i,1,k) comp -= merge(u[i], v[i]);
    if(comp == 1){ cout<<ans<<endl; return 0; }
    rep(i,0,n-1) if((comp -= merge(i, (i+m)%n)) == 1){
        cout<< ans+i+1 <<endl; return 0;
    }
    cout<<"-1\n";
    return 0;
}
posted @ 2021-12-14 20:21  Neal_lee  阅读(85)  评论(0编辑  收藏  举报