传送门
思路:
① 可以把每个学生都看作点,而那些实力相同的学生就处在同一个连通块内,因为连通块内的同学要么都取,要么不取,所以可以将连通块缩成一个点。只需用并查集维护每个连通块的大小。
② 接着采取神奇的可行性DP:用 f[ i ] 判断选出 i 个学生的方案是否可行。可行就转移 。
伪代码:
初始化: f[ 0 ] =true;
for( j: 1~n ) //枚举点
for( i: n-size ~0 ) //逆序,,
if( f[ i ] )
f [ i - size ] =true.( size 为枚举到的点大小)
③ 最后只要暴力地从 m 左右两边找出离 m 最近的 f[ i ] == true,就是答案。
Code:
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<string> #include<cstdlib> #include<stack> #include<vector> #include<queue> #include<deque> #include<map> #include<set> using namespace std; #define lck_max(a,b) ((a)>(b)?(a):(b)) #define lck_min(a,b) ((a)<(b)?(a):(b)) typedef long long LL; const int maxn=4e5+7; inline LL lck_abs(LL a) {return a>0?a:-a;} inline LL read() { LL kr=1,xs=0; char ls; ls=getchar(); while(!isdigit(ls)) { if(!(ls^45)) kr=-1; ls=getchar(); } while(isdigit(ls)) { xs=(xs<<1)+(xs<<3)+(ls^48); ls=getchar(); } return xs*kr; } inline void out(LL xs) { if(!xs) {putchar(48); return;} if(xs<0) putchar('-'),xs=-xs; int kr[57],ls=0; while(xs) kr[++ls]=xs%10,xs/=10; while(ls) putchar(kr[ls]+48),ls--; } LL fa[maxn],si[maxn];//si 维护连通块大小 inline LL find(LL u) { return fa[u]==u?fa[u]:find(fa[u]); } LL n,m,k,u,v,i,j,l,r,ans,top,st[maxn];bool f[maxn]; int main() { n=read();m=read();k=read(); for(i=1;i<=n;i++) fa[i]=i,si[i]=1; for(i=1;i<=k;i++) { u=read();v=read(); LL r1=find(u),r2=find(v); if(r1==r2) continue; fa[r2]=r1,si[r1]+=si[r2]; } for(LL i=1;i<=n;i++) if(fa[i]==i) st[++top]=i;//缩点 f[0]=true; for(i=1;i<=top;i++)//枚举点 for(j=n-si[st[i]];j>=0;j--)//可达性DP if(f[j]) f[j+si[st[i]]]=true; for(i=m;!f[i];i--); l=m-i;//找到比m小的最接近的人数 for(i=m;!f[i];i++); r=i-m;//找到比m大的最接近的人数 if(r>=l) out(m-l),putchar(' '); else out(m+r),putchar(' '); return 0; }