1386:打击犯罪(并查集)
【题目描述】
某个地区有n(n≤1000)个犯罪团伙,当地警方按照他们的危险程度由高到低给他们编号为1-n,他们有些团伙之间有直接联系,但是任意两个团伙都可以通过直接或间接的方式联系,这样这里就形成了一个庞大的犯罪集团,犯罪集团的危险程度由集团内的犯罪团伙数量唯一确定,而与单个犯罪团伙的危险程度无关(该犯罪集团的危险程度为n)。现在当地警方希望花尽量少的时间(即打击掉尽量少的团伙),使得庞大的犯罪集团分离成若干个较小的集团,并且他们中最大的一个的危险程度不超过n/2。为达到最好的效果,他们将按顺序打击掉编号1到k的犯罪团伙,请编程求出k的最小值。
【题目链接】
http://ybt.ssoier.cn:8088/problem_show.php?pid=1386
【算法】
1.二分/倍增+并查集(每次尝试都要重新构造并查集)
2.逆序加点(维护一个并查集即可,不知道为啥,这个耗时更长。。。估计是稀疏图用邻接矩阵就很尴尬了)
【代码1】
1 #include <bits/stdc++.h> 2 using namespace std; 3 int n; 4 int G[1100][1100],fa[1100]; 5 int Get(int x) 6 { 7 if(fa[x]==x) return x; 8 return fa[x]=Get(fa[x]); 9 } 10 void Merge(int x,int y) 11 { 12 fa[Get(x)]=Get(y); 13 } 14 bool valid(int k) 15 { 16 multiset<int> s; 17 for(int i=k+1;i<=n;i++) fa[i]=i; 18 for(int i=k+1;i<=n;i++) 19 for(int j=k+1;j<=n;j++) 20 if(G[i][j]) Merge(i,j); 21 for(int i=k+1;i<=n;i++) { 22 s.insert(Get(i)); 23 if(s.count(Get(i))>n/2) return false; 24 } 25 return true; 26 } 27 int main() 28 { 29 scanf("%d",&n); 30 for(int i=1;i<=n;i++) { 31 int a,num; scanf("%d",&num); 32 while(num--) scanf("%d",&a),G[i][a]=1; 33 } 34 int l=1,r=n/2+1; 35 while(l<r) { 36 int mid=l+r>>1; 37 if(valid(mid)) r=mid; 38 else l=mid+1; 39 } 40 printf("%d\n",l); 41 return 0; 42 }
【代码2】
1 #include <bits/stdc++.h> 2 using namespace std; 3 int n; 4 int fa[1100],G[1100][1100]; 5 int Get(int x) 6 { 7 if(x==fa[x]) return x; 8 return fa[x]=Get(fa[x]); 9 } 10 void Merge(int x,int y) 11 { 12 fa[Get(x)]=Get(y); 13 } 14 int main() 15 { 16 scanf("%d",&n); 17 for(int i=1;i<=n;i++) { 18 int num,a; scanf("%d",&num); 19 while(num--) scanf("%d",&a),G[i][a]=1; 20 } 21 for(int i=1;i<=n;i++) fa[i]=i; 22 for(int i=n;i>=1;i--) { 23 multiset<int> s; bool flag=1; 24 for(int j=i+1;j<=n;j++) { 25 if(G[i][j]) Merge(i,j); 26 if(G[j][i]) Merge(i,j); 27 } 28 for(int j=i;j<=n;j++) { 29 s.insert(Get(j)); 30 if(s.count(Get(j))>n/2) flag=0; 31 } 32 if(!flag) { printf("%d\n",i); return 0; } 33 } 34 }