思路
首先,需要明白,如果在操作一后,有 x x x 个联通块,则整张图还需要 x − 1 x-1 x−1 条无向边才能联通。
而,设第 i i i 个联通块的大小是 s i z i siz_i sizi,由于每个点只能连一条边,则最多能向外连到 s i z i siz_i sizi 个点,等价于可以向外连 s i z i siz_i sizi 个联通块(即每个点都在不同的联通块)。
对于操作 2 2 2,一个联通块就只能向外连 k k k 条边,所以就只能联通 min ( s i z i , k ) \min(siz_i,k) min(sizi,k) 个块。
所以,操作二在满足 ∑ i = 1 x min ( s i z i , k ) ≥ 2 × ( x − 1 ) \displaystyle\sum_{i=1}^{x} \min(siz_i,k)\ge 2\times(x-1) i=1∑xmin(sizi,k)≥2×(x−1) 才能使图联通,注意连接两个联通块必须两个都有可以向外连的边时才行,所以右边要乘 2 2 2。
那么就可以用贪心的思想从小到大合并联通块。证明如下:
如果两个联通块 x , y x,y x,y,不妨设 s i z x < s i z y siz_x<siz_y sizx<sizy,满足 s i z x ≤ k , s i z y ≤ k , s i z x + s i z y > k siz_x\le k,siz_y\le k,siz_x+siz_y>k sizx≤k,sizy≤k,sizx+sizy>k,且存在另一个联通块 z z z 使得 s i z x + s i z z ≤ k siz_x+siz_z\le k sizx+sizz≤k,易得 s i z z < s i z y siz_z<siz_y sizz<sizy。这样,第一种方法的贡献是 k + s i z z k+siz_z k+sizz,而第二种就是 s i z x + s i z y + s i z z siz_x+siz_y+siz_z sizx+sizy+sizz,因为 s i z x + s i z y > k siz_x+siz_y>k sizx+sizy>k,所以第二种一定比第一种优。
综上,先选小的比先选大的更优。
这个东西用堆维护就行了,时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int n,m,k,f[N],siz[N],ans,now;
priority_queue<int> q;
int find(int x)
{
if(f[x]==x) return x;
return f[x] = find(f[x]);
}
void add(int x,int y)//维护联通块
{
int fx = find(x),fy = find(y);
if(fx==fy) return;
siz[fx]+=siz[fy],siz[fy] = 0,f[fy] = fx;
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m>>k;
for(int i = 1;i<=n;i++) f[i] = i,siz[i] = 1;
for(int i = 1,x,y;i<=m;i++)
cin>>x>>y,add(x,y);
for(int i = 1;i<=n;i++)
if(f[i]==i)
q.push(-siz[i])/*大根堆维护最小值转负数*/,now+=min(siz[i],k);
while(1)
{
if(now>=2*q.size()-2)//满足条件
return cout<<ans,0;
ans++;
int u = -q.top();q.pop();
int v = -q.top();q.pop();
now+=min(k,u+v)-min(k,u)-min(k,v);
q.push(-u-v);
}
return 0;
}