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 }
独立意志与自由思想是必须争的,且须以生死力争。