题目大意
有
n
n
个人,初始一个人一个联通块,编号都是。
每次两个联通块合并,假设较小联通块大小是
x
x
,较大联通块编号为的会与较小联通块大小是
y mod x
y
m
o
d
x
的连边,然后编号变成
y+x
y
+
x
,接下来合并两个联通块。
最终所有人都在一个联通块内。
现在给你最终联通块中所有连边情况,已知一天里联通块合并可以并行,问最少多少天能合并出来。
做法
考虑一个递归过程,任意时刻联通块对应最终联通块一个编号区间。
我们要尝试分割它,满足题目所说的条件。
假设是分割
0
0
到。
设
n−1
n
−
1
连的最小编号是
x
x
,因为一定在较大联通块,所以
x
x
一定在较小联通块。
假设较小联通块大小为,有
(n−1) mod y=x
(
n
−
1
)
m
o
d
y
=
x
。
如果两个联通块不相等,因为后面那个更大,显然可以找到
(n−x−2) mod y=y−1
(
n
−
x
−
2
)
m
o
d
y
=
y
−
1
。
那么
n−x−2
n
−
x
−
2
连向的最小编号就是分界线。
能找分界线本题显然就能解决。
#include<cstdio>
#include<algorithm>
#include<vector>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define pb push_back
using namespace std;
const int maxn=100000+10;
vector<int> e[maxn];
int i,j,k,l,t,n,m,tot,ans,ca;
bool czy;
int solve(int l,int r){
if (l==r&&!e[r].size()) return 0;
if (l>r||!e[r].size()) return -1;
int mid=e[r].back();
if (r-mid>mid-l+1){
if (!e[r-(mid-l+1)].size()) return -1;
int tmp=e[r-(mid-l+1)].back();
if (tmp<mid) return -1;
mid=tmp;
}
if (r-mid<mid-l+1||mid<l) return -1;
int now=(r-mid-1)%(mid-l+1);
int i;
fd(i,r,mid+1){
if (!e[i].size()) return -1;
if (e[i].back()-l!=now) return -1;
e[i].erase(e[i].end()-1);
if (now==0) now=mid-l;else now--;
}
int j=solve(l,mid),k=solve(mid+1,r);
if (j<0||k<0) return -1;
else return max(j,k)+1;
}
int main(){
freopen("gymnastics.in","r",stdin);freopen("gymnastics.out","w",stdout);
scanf("%d",&ca);
while (ca--){
scanf("%d%d",&n,&m);
fo(i,0,n-1) e[i].clear();
fo(i,1,m){
scanf("%d%d",&j,&k);
if (j<k) swap(j,k);
e[j].pb(k);
}
fo(i,0,n-1){
sort(e[i].begin(),e[i].end());
reverse(e[i].begin(),e[i].end());
}
ans=solve(0,n-1);
printf("%d\n",ans);
}
}