选学霸
题目描述
老师想从 n n n 名学生中选 m m m 人当学霸,但有 k k k 对人实力相当,如果实力相当的人中,一部分被选上,另一部分没有,同学们就会抗议。所以老师想请你帮他求出他该选多少学霸,才能既不让同学们抗议,又与原来的 m m m 尽可能接近。
输入格式
第一行,三个正整数 n , m , k n,m,k n,m,k。
接下来 k k k 行,每行 2 2 2 个数,表示一对实力相当的人的编号(编号为 1 , 2 , ⋯ n 1,2,\cdots n 1,2,⋯n)。
输出格式
共一行,表示既不让同学们抗议,又与原来的 m m m 尽可能接近的选出学霸的数目。
如果有两种方案与 m m m 的差的绝对值相等,选较小的一种。
样例 #1
样例输入 #1
4 3 2
1 2
3 4
样例输出 #1
2
提示
对于 100 % 100\% 100% 的数据,满足 1 ≤ n , m ≤ 2 × 1 0 4 1 \le n,m \le 2 \times 10^4 1≤n,m≤2×104。
思路
因为题目说实力相同的人捆绑起来,如果要选,他们所有人都要被选,那么全部人都要被选,如果不选,那都不选。
- 对于捆绑的,我们可以考虑连通性问题,那就是并查集了。
- 然后后面就是用背包了,因为体积有限。(其实这叫做虚的体积,对于这种题眼:表示既不让同学们抗议,又与原来的
m
m
m 尽可能接近的选出学霸的数目。这样的我们都不能把它看成是总体积,此时只能把总人数看成总体积,然后我们不是用并查集分好组了吗,那么我们可以让他们选和不选。就类似01背包问题那样做,只不过此时
f[i]
装的是bool值,表示在该体积下是否能按照符合题意选的方案(保证不冲突))。 - 然后我们最后从小到达枚举一下就可以了。
总的简化思路:
- 并查集分组。
- 优化版01背包选与不选看看是否能解决不冲突问题,是的话就是true。
代码
//首先,我们并查集(我们把实力相同的人放在一起)
//就我们没实力!!!!
//好吧,捆绑销售是吧
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N = 2e5+10;
int p[N];
int f[N],siz[N],v[N];
int n,m,k;
int cnt;//记录到底有多少个水平相同的学霸
int find(int x){
if(x!=p[x])p[x]=find(p[x]);
//不要随随便便路径压缩,只有跟树上操作的时候才搞
return p[x];
}
signed main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++)p[i]=i,siz[i]=1;
for(int i=1;i<=k;i++){
int a,b;
cin>>a>>b;
int pa=find(a),pb=find(b);
if(pa!=pb){
p[pa]=pb;
siz[pb]+=siz[pa];
}
}
for(int i=1;i<=n;i++){
if(p[i]==i){
v[++cnt]=siz[i];
}
}
//背包
f[0]=1;
for(int i=1;i<=cnt;i++){
for(int j=n;j>=v[i];j--){
f[j]+=f[j-v[i]];
}
}
int res=0;
for(int i=0;i<=n;i++){
if(f[i]&&abs(res-m)>abs(i-m)){
res=i;
}
}
cout<<res;
return 0;
}