b_lg_选学霸(并查集+01背包)

老师想从 n 名学生中选 m 人当学霸,但有 k 对人实力相当,如果实力相当的人中,一部分被选上,另一部分没有,同学们就会抗议。
所以老师想请你帮他求出他该选多少学霸,才能既不让同学们抗议,又与原来的 m 尽可能接近。

思路
注意:是与m的差的绝对值最小化,所以选的人数可>m,也可≤m

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e4+5;
int n,m,k, fa[N], sz[N], f[N], s[N];
int find(int u) {
    return fa[u]==u ? u : fa[u]=find(fa[u]);
}
void merge(int u, int v) {
    int fu=find(u), fv=find(v);
    if (fu!=fv) {
        fa[fu]=fv;
        sz[fv]+=sz[fu];
    }
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin>>n>>m>>k;
    for (int i=1; i<=n; i++) fa[i]=i, sz[i]=1;
    for (int i=0; i<k; i++) {
        int u,v; cin>>u>>v;
        merge(u,v);
    }
    int tot=0, cap=min(n,2*m);
    for (int i=1; i<=n; i++) if (fa[i]==i)
        s[tot++]=sz[i];  //每个连通块的人数
    //对于每个连通块都可以选或不选
    for (int i=0; i<tot; i++)
    for (int j=cap; j>=s[i]; j--) {
        f[j]=max(f[j], f[j-s[i]]+s[i]);
    }
    ll ans=-1, mi=INT_MAX;
    for (int j=0; j<=cap; j++) {
        int d=abs(f[j]-m);
        if (d<mi || (d==mi && f[j]<ans)) {
            mi=d, ans=f[j];
        }
    }
    cout<<ans;
    return 0;
}
原文地址:https://www.cnblogs.com/wdt1/p/13807241.html