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\) 轮二分图必然连通,像这样:
![](https://img2020.cnblogs.com/blog/2127546/202112/2127546-20211214201933459-1804273472.png)
竖线两侧分别为左右部点,这样标号以后,容易发现这实际上即 \(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;
}