Luogu P2170选学霸【并查集+背包】By cellur925

题目传送门

开始看到本题完全认为就是个彻头彻尾的并查集,只要把实力相当的人都并到一个集合中,最后再找一共有多少联通块即可。

后来发现这是大错特错的qwq。因为选了一个集合中的某人,那这个集合中所有人就要都选。

理解题意并不透彻:这个集合中的所有人,要么都不选,要么都得选。其实到这里就可以看出,它其实是肥肠满足01背包的性质,每个集合(可以打包看成一个物品)要么选,要么不选,只有两种决策,这个物品的体积和价值都是集合内学生的个数。

然后,这又是一种可行性背包类型:因为题目要求与给定值最接近的选出的学霸数量,所以以f[i]表示选出i个学生是否可行。

转移和01背包类似,之前在USACO中也做过类似的题目,只是当时没太注意了。这类题目看起来一点也不像背包,但是可以这么求解。

其他细节:记录各集合人数的时候最后需要重新路径压缩一下,确保找到最“根”的父亲

     dp赋初值!dp[0]=1;

     因为要找最接近的,所以可能比基准值大一些,也可能比基准值小一些,所以数组和最后枚举求解都开到2*m。

 1 #include<cstdio>
 2 #include<algorithm>
 3 
 4 using namespace std;
 5 
 6 int n,m,k,tot;
 7 int f[40000],tong[40000],good[40000];
 8 bool dp[80000];
 9 
10 int getf(int x)
11 {
12     if(f[x]==x) return x;
13     else return getf(f[x]); 
14 }
15 
16 int main()
17 {
18     scanf("%d%d%d",&n,&m,&k);
19     for(int i=1;i<=n;i++) f[i]=i;
20     for(int i=1;i<=k;i++)
21     {
22         int x=0,y=0;
23         scanf("%d%d",&x,&y);
24         int pp=getf(x);
25         int qq=getf(y);
26         if(qq!=pp) f[qq]=pp;    
27     }
28     for(int i=1;i<=n;i++)
29         tong[getf(i)]++;
30     for(int i=1;i<=n;i++)
31         if(tong[i]) good[++tot]=tong[i];
32     dp[0]=1;
33     for(int i=1;i<=tot;i++)
34          for(int j=2*m;j>=good[i];j--)
35              if(dp[j-good[i]])
36                  dp[j]=1;
37     for(int i=0;i<=m;i++)
38     {
39         if(dp[m-i])
40         {
41             printf("%d",m-i);
42             return 0;
43         }
44         if(dp[m+i])
45         {
46             printf("%d",m+i);
47             return 0;
48         }
49     }
50     return 0;
51 }
View Code
原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9673246.html