题目描述 Description
老师想从N名学生中选M人当学霸,但有K对人实力相当,如果实力相当的人中,一部分被选上,另一部分没有,同学们就会抗议。所以老师想请你帮他求出他该选多少学霸,才能既不让同学们抗议,又与原来的M尽可能接近。
输入描述 Input Description
第一行,三个正整数N,M,K。
第2…K行,每行2个数,表示一对实力相当的人的编号(编号为1…N)。
输出描述 Output Description
一行,表示既不让同学们抗议,又与原来的M尽可能接近的选出学霸的数目。(如果有两种方案与M的差的绝对值相等,选较小的一种。)
样例输入 Sample Input
4 3 2
1 2
3 4
样例输出 Sample Output
2
数据范围及提示 Data Size & Hint
100%的数据N,P<=30000
思路
先用并查集划分层次,然后把层次视为一件物品,做剩余空间最小的背包问题。
但是由于这里问的是相差最小,即绝对值最小,所以这个背包空间要开两倍,然后在从容量满的那个点,向左向右同时进行查找,输出第一个答案
代码(奇丑无比)
#include <iostream>
using namespace std;
const int maxn=1000000;
int root[maxn],v[maxn],c[maxn]={1};
int find(int x){
if(root[x]!=x)//用递归来寻找根节点,可以使得树的深度保持在2,不至于在找根节点花太多时间
root[x]=find(root[x]);
return root[x];
}
void unionn (int x,int y){
int rx=find(x);
int ry=find(y);
if(rx!=ry) {
root[rx]=ry;//如果两个点不在一棵树内,那么吧x这个树的跟节点给挂到y这个树的根节点上面
v[y]+=v[x];//吧x层次的人数转移到y旗下
v[x]=0;//设置x层次人数为0
}
}
void first(int n){
for(int i=1;i<=n;i++) {
root[i]=i;//初始化每一个点的根节点为自身
v[i]=1;//初始化每一个层次的人数为1
}
}
int main(){
int n,m,k,V;//老师想从N名学生中选M人当学霸,但有K对人实力相当
cin>>n>>m>>k;
first(n);//初始化
for(int i=1,a,b;i<=k;i++){
cin>>a>>b;
unionn(a,b);//合并两个人所在的层次
}
V=2*m;//因为要绝对值相差最小,所以背包动规空间要X2
for(int i=1;i<=n;i++)//遍历每一个层次(物品)
if(v[i]!=0)//如果这个层次存在学生
for(int j=V-v[i];j>=0;j--)//这段代码和装箱问题类似
if(c[j]==1)
c[j+v[i]]=c[j];
for(int i=0;i<=m;i++) {//寻找绝对值差最小的那个方法并输出
if(c[m-i]==1){
cout<<m-i;
break;
}
if(c[m+i]==1){
cout<<m+i;
break;
}
}
return 0;
}