1、7-13 红色警报 (25 分)
思路:一道并查集的问题,这题问我们删去了一个城市后连通性是否会受到影响,那我们就进行模拟,先统计原来有几个块,然后再删去这个块后继续统计有多少个连通块,进行比较就知道连通性是否受到了影响。
//sort快排模拟归并和选择
#include<bits/stdc++.h>
using namespace std;
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 1e6+10;
int n,m,u,k,z,last,now;
int f[maxn],vis[maxn],x[maxn],y[maxn];
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
void merge(int a, int b){
a=find(a);
b=find(b);
if(a!=b) f[a]=b;
}
int getsum(){
int cnt=0;
for(int i=0; i<n; i++) if(f[i]==i) cnt++;
return cnt;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++) f[i]=i;
for(int i=0; i<m; i++){
scanf("%d%d",&x[i],&y[i]);
merge(x[i],y[i]);
}
last=getsum();
scanf("%d",&k);
int ans=0;
while(k--){
scanf("%d",&u);
if(!vis[u]) ans++,vis[u]=1;
for(int i=0; i<n; i++) f[i]=i;
for(int i=0; i<m; i++){
if(vis[x[i]]||vis[y[i]]) continue;
merge(x[i],y[i]);
}
now=getsum();
if(now==last||now-1==last) printf("City %d is lost.\n",u);
else printf("Red Alert: City %d is lost!\n",u);
if(ans==n) puts("Game Over.");
last=now;
}
return 0;
}
2、7-9 家庭房产 (25 分)
思路:这道题数据处理上比较恶心,我们开两个结构体,一个用于处理输入数据,一个用于输入后数据的处理。这道题显然是一个并查集合并的问题,我们可以直接让所有成员向第一个成员合并,合并工程中记录成员出现过,然后后面按顺序查找即可找到最小编号的成员,这是代码比较灵活的一部分,具体看代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4+10;
int f[maxn],vis[maxn];
//vis数组的作用是标记出现过的点,这样在后面我们从头开始找就可以找到每个家庭最小的编号了
struct node{
int id;
double mon,area;
}a[maxn];//存初始的数据
struct nodex{
int idx,num;
double sumx,areax;
double avgsumx,avgareax;
}b[maxn];//存转移及处理后的数据
bool cmp(nodex x, nodex y){
if(x.avgareax==y.avgareax) return x.idx<y.idx;
return x.avgareax>y.avgareax;
}//排序规则
int find(int x){
if(x!=f[x]) f[x]=find(f[x]);
return f[x];
}
void merge(int a, int b){
a=find(a),b=find(b);
if(a!=b) f[b]=a;
}
int n;
int main(){
scanf("%d",&n);
for(int i=0; i<10010; i++) f[i]=i;
for(int i=0; i<n; i++){
int x,px,py,k;
//这里的并查集全部合并到头就好
scanf("%d%d%d%d",&x,&px,&py,&k);
a[i].id=x;
vis[x]=1;
if(px!=-1){
merge(x,px);
vis[px]=1;
}
if(py!=-1){
merge(x,py);
vis[py]=1;
}
while(k--){
int xx;
scanf("%d",&xx);
merge(x,xx);
vis[xx]=1;
}
scanf("%lf%lf",&a[i].mon,&a[i].area);
}
for(int i=0; i<n; i++){
int x=find(a[i].id);
b[x].sumx+=a[i].mon;
b[x].areax+=a[i].area;
}
for(int i=0; i<10010; i++){
if(vis[i]){
f[i]=find(i);
if(b[f[i]].num==0) b[f[i]].idx=i;
b[f[i]].num++;
//处理数据
b[f[i]].avgsumx=b[f[i]].sumx*1.0/b[f[i]].num;
b[f[i]].avgareax=b[f[i]].areax*1.0/b[f[i]].num;
}
}
sort(b,b+10000,cmp);
int ans=0;
for(int i=0; i<10000; i++){
if(b[i].num) ans++;
else break;
}
printf("%d\n",ans);
for(int i=0; i<ans; i++){
printf("%04d %d %.3f %.3f\n",b[i].idx,b[i].num,b[i].avgsumx,b[i].avgareax);
}
return 0;
}
3、7-9 部落 (25 分)
思路:并查集问题,这道题的并查集要注意的一个点是有可能出现后面成员的祖先不是本身而是上面已经出现过的,这个时候我们只需让并查集向小的进行合并即可。剩下的就不难做了。
PS:其实后面经对并查集的深入了解后,不用管是否要向小的合并,因为你在找有多少个
部落的同时,已经利用find函数不断地在刷新并查集f数组的祖先了。
#include <bits/stdc++.h>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;
int f[maxn],vis[maxn];
set <int> s;
int n,m,k,x,y,z;
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
void merge(int a, int b){
a=find(a);
b=find(b);
if(f[a]<f[b]) f[b]=f[a];
//往小的合并
else f[a]=f[b];
}
/*
void merge(int a, int b){
a=find(a);
b=find(b);
if(a!=b) f[a]=b;
}也可以直接写成这样
*/
int main(){
scanf("%d",&n);
for(int i=1; i<=10000; i++) f[i]=i;
for(int i=1; i<=n; i++){
scanf("%d",&k);
scanf("%d",&x);
s.insert(x);
for(int j=1; j<=k-1; j++){
scanf("%d",&y);
s.insert(y);
merge(x,y);
x=y;
}
}
int cnt=0;
/*// for(int i=1; i<=s.size(); i++) cout << f[i] << endl;*/
for(auto i:s){
int u=find(i);
if(!vis[u]) cnt++,vis[u]=1;
}
printf("%d %d\n",s.size(),cnt);
scanf("%d",&m);
while(m--){
scanf("%d%d",&y,&z);
if(find(y)==find(z)) puts("Y");
else puts("N");
}
return 0;
}
4、7-8 朋友圈 (25 分)
思路:和部落一样,向小的合并
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int f[maxn];
int find(int x){
if(x!=f[x]) f[x]=find(f[x]);
return f[x];
}
void merge(int a, int b){
a=find(a),b=find(b);
if(a>b) f[a]=b;
else f[b]=a;
}
int n,m;
int vis[maxn];
int cnt[maxn];
int main(){
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) f[i]=i;
for(int i=1; i<=m; i++){
int k;
scanf("%d",&k);
int xx;
scanf("%d",&xx);
k--;
while(k--){
int x;
scanf("%d",&x);
merge(xx,x);
}
}
for(int i=1; i<=n; i++){
int y=find(i);
cnt[y]++;
}
sort(cnt+1,cnt+1+n);
printf("%d",cnt[n]);
return 0;
}
5、7-10 社交集群 (25 分)
思路:并查集,直接向第一个爱好进行合并,但是要注意的是求的是人数,所以我们要开一个cnt数组,记录每个人的第一个爱好,这样可以方便后面统计每个朋友圈的人数,以及朋友圈的个数,具体看代码。
//思路:直接以每个人第一个爱好为父节点进行合并
#include <bits/stdc++.h>
#define MAX 0x3f3f3f3f
using namespace std;
const int maxn = 1e5+10;
int f[maxn],res[maxn],cnt[maxn],sum;
char ch;
int n,k;
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
void merge(int a, int b){
a=find(a), b=find(b);
if(a!=b) f[b]=a;
}
int main(){
cin >> n;
for(int i=1; i<=1000; i++) f[i]=i;
for(int i=1; i<=n; i++){
cin >> k >> ch >> cnt[i];
for(int j=1; j<=k-1; j++){
int x;
cin >> x;
merge(cnt[i],x);
}
}
for(int i=1; i<=n; i++){
res[find(cnt[i])]++;
}
for(int i=1; i<=1000; i++){
if(res[i]!=0) sum++;
}
sort(res+1,res+1001,greater<int>());
printf("%d\n",sum);
for(int i=1; i<=sum; i++){
if(i!=1) printf(" ");
printf("%d",res[i]);
}
return 0;
}
总结一下:
在做并查集题目的时候,我们尽管先找一个对象进行合并,然后后续操作里我们的find函数会帮我们自动进行祖先修改。这时我们就可以求出有多少个集合或者其他的数据了,或者同前面的家庭房产一般进行一定的数据处理。