并查集(红色警报+奶酪+关押罪犯+食物链)

红色警报

战争中保持各个城市间的连通性非常重要。本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报。注意:若该国本来就不完全连通,是分裂的k个区域,而失去一个城市并不改变其他城市之间的连通性,则不要发出警报。

输入格式:
输入在第一行给出两个整数N(0 < N ≤ 500)和M(≤ 5000),分别为城市个数(于是默认城市从0到N-1编号)和连接两城市的通路条数。随后M行,每行给出一条通路所连接的两个城市的编号,其间以1个空格分隔。在城市信息之后给出被攻占的信息,即一个正整数K和随后的K个被攻占的城市的编号。
注意:输入保证给出的被攻占的城市编号都是合法的且无重复,但并不保证给出的通路没有重复。
输出格式:
对每个被攻占的城市,如果它会改变整个国家的连通性,则输出Red Alert: City k is lost!,其中k是该城市的编号;否则只输出City k is lost.即可。如果该国失去了最后一个城市,则增加一行输出Game Over.。

输入样例:
5 4
0 1
1 3
3 0
0 4
5
1 2 0 4 3
输出样例:
City 1 is lost.
City 2 is lost.
Red Alert: City 0 is lost!
City 4 is lost.
City 3 is lost.
Game Over.

膜拜写出并查集算法的大佬

#include<iostream>
#include<cstdio>
using namespace std;
struct node{int u,v;}city[10010];
int fa[10010],dam[10010],n;//damage[];
int cnt,newcnt;
int find(int x){return fa[x]==x?fa[x]:fa[x]=find(fa[x]);}
int main(){
	int m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&city[i].u,&city[i].v);
		int u=find(city[i].u),v=find(city[i].v);
		if(u!=v) fa[u]=v;
	}//把连通的城市放到一个并查集里
	for(int i=0;i<n;i++)
		if(fa[i]==i) cnt++;//统计连通区域的个数
	int k,x;
	scanf("%d",&k);	
	for(int dmg=1;dmg<=k;dmg++){
		scanf("%d",&x);
		dam[x]=1;
		for(int i=0;i<n;i++) fa[i]=i;
		for(int i=1;i<=m;i++)
			if(!dam[city[i].u]&&!dam[city[i].v]){
				int u=find(city[i].u),v=find(city[i].v);
				if(u!=v) fa[u]=v;
			}//破坏几个城市后再次把连通的城市放到一个并查集里
		newcnt=0;	
		for(int i=0;i<n;i++)
			if(fa[i]==i&&!dam[i]) newcnt++;//再次统计连通区域的个数
		if(newcnt==cnt||newcnt+1==cnt) printf("City %d is lost.\n",x);
		else printf("Red Alert: City %d is lost!\n",x);	
		cnt=newcnt;	
	}
	if(k==n) printf("Game Over.\n");
	return 0;	
} 

奶酪

题目描述
现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪 中间有许多 半径相同 的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中, 奶酪的下表面为z = 0 z 0_z 0z=0,奶酪的上表面为z = h z h_z hz=h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐 标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别 地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果 一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在 不破坏奶酪 的情况下,能否利用已有的空洞跑 到奶酪的上表面去?
空间内两点 P 1 ( x 1 , y 1 , z 1 ) P_1(x_1,y_1,z_1) P1(x1,y1,z1) P 2 ( x 2 , y 2 , z 2 ) P_2(x_2,y_2,z_2) P2(x2,y2,z2) 的距离公式如下:
d i s t ( P 1 , P 2 ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 \mathrm{dist}(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} dist(P1,P2)=(x1x2)2+(y1y2)2+(z1z2)2

输入格式
每个输入文件包含多组数据。
第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。
接下来是 T 组数据,每组数据的格式如下: 第一行包含三个正整数 n,h 和 r,两个数之间以一个空格分开,分别代表奶酪中空 洞的数量,奶酪的高度和空洞的半径。
接下来的 n 行,每行包含三个整数 x,y,z,两个数之间以一个空格分开,表示空 洞球心坐标为(x,y,z)。
输出格式
T 行,分别对应 T组数据的答案,如果在第 i 组数据中,Jerry 能从下 表面跑到上表面,则输出Yes,如果不能,则输出No (均不包含引号)。

输入输出样例
输入
3
2 4 1
0 0 1
0 0 3
2 5 1
0 0 1
0 0 4
2 5 2
0 0 2
2 0 4
输出
Yes
No
Yes

暴力做法:O(n!)
应用并查集:O(n2)

核心代码:
int find(int x){if(f[x]!=x) f[x]=find(f[x]);return f[x];}
#include<cmath>
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
const int maxn=1e6;
ll f[maxn],x[maxn],y[maxn],z[maxn],s1[maxn],s2[maxn];
ll find(ll x){if(x!=f[x]) f[x]=find(f[x]);return f[x];}
double dis(ll x1,ll y1,ll z1,ll x2,ll y2,ll z2){
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
}
int main(){
    int t;scanf("%d",&t);
    ll n,h,r;
    while(t--){
        bool ok=0;
        scanf("%lld%lld%lld",&n,&h,&r);
        for(int i=1;i<=n;i++) f[i]=i;
        int tot1=0,tot2=0;
        for(int i=1;i<=n;i++){
            scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
            if(z[i]<=r) s1[++tot1]=i;//分别记录与上下面相交的洞的个数
            if(z[i]+r>=h) s2[++tot2]=i;
            for(int j=1;j<i;j++)
             if(dis(x[i],y[i],z[i],x[j],y[j],z[j])<=2*r){
                 if(find(i)!=find(j)) f[find(j)]=find(i);
             }
        }
        for(int i=1;i<=tot1;i++)
         for(int j=1;j<=tot2;j++)
          if(find(s1[i])==find(s2[j])){ok=1;break;}
        if(ok) puts("Yes");
         else puts("No");
    }
    return 0;
}

关押罪犯

题目描述
S 城现有两座监狱,一共关押着 NN 名罪犯,编号分别为 1-N1−N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 cc 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 cc 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

输入格式
每行中两个数之间用一个空格隔开。第一行为两个正整数 N,M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 M 行每行为三个正整数a,b,c,表示a号和b号罪犯之间存在仇恨,其怨气值为c,每对罪犯组合只出现一次。
对于 30%的数据有 N≤15。
对于 70% 的数据有 N≤2000,M≤50000。
对于 100% 的数据有 N≤20000,M≤100000。
输出格式
共 1行,为 Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0。

输入输出样例
输入
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
输出
3512

暴力:O(n!)
并查集:O(nlogn)

核心思想:种类并查集
int fa[50010],n;//开一个双倍的并查集。1~n存朋友,n+1~n+n存敌人。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct people{int u,v,w;}p[100010];
int fa[50010],n;//开一个双倍的并查集。1~n存朋友,n+1~n+n存敌人。
bool cmp(people x,people y){return x.w>y.w;}
int find(int x){return fa[x]==x?fa[x]:fa[x]=find(fa[x]);}
int main(){
	int m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].w);
	sort(p+1,p+m+1,cmp);
	for(int i=1;i<=n;i++) fa[i]=i,fa[i+n]=i+n;
	int cfl=0;
	for(int i=1;i<=m;i++){
		int f1=find(p[i].u),f2=find(p[i].v),f3=find(p[i].u+n),f4=find(p[i].v+n);
		if(f1==f2){cfl=p[i].w;break;}
		fa[f1]=f4;
		fa[f3]=f2; 
	}
	printf("%d",cfl);
	return 0;  
} 

食物链

题目描述
动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A。
现有 N 个动物,以 1 - N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 N 个动物所构成的食物链关系进行描述:
第一种说法是 1 X Y,表示 X 和 Y 是同类。
第二种说法是2 X Y,表示 X 吃 Y 。
此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
当前的话与前面的某些真的话冲突,就是假话
当前的话中 X 或 Y 比 N 大,就是假话
当前的话表示 X 吃 X,就是假话
你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式
第一行两个整数,N,K,表示有 N 个动物,K 句话。
第二行开始每行一句话(按照题目要求,见样例)
1 ≤ N ≤ 5 ∗ 10^4
1 ≤ K ≤ 10^5
输出格式
一行,一个整数,表示假话的总数。

输入输出样例
输入
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出
3

暴力:这道题暴力做的出来算你牛
并查集:线性时间

核心代码:int fa[150010];//三倍大小的数组,存自己,天敌和食物
#include<iostream>
#include<cstdio>
using namespace std;
int fa[150010];//三倍大小的数组,存自己,天敌和食物
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int main(){
	int n,m,op,x,y,cnt=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=3*n;i++) fa[i]=i;
	while(m--){
		scanf("%d%d%d",&op,&x,&y);
		if(x>n||y>n) cnt++;
		else{
		    int x1=find(x),y1=find(y),x2=find(x+n),y2=find(y+n),x3=find(x+2*n),y3=find(y+2*n);
		    if(op==1){
			    if(x1==y2||x1==y3) cnt++;
			    else{
				    fa[x1]=y1;
				    fa[x2]=y2;
				    fa[x3]=y3;
			    }
		    }
		    else{
			    if(x1==y1||x1==y3) cnt++;
			    else{
				    fa[x1]=y2;
				    fa[x2]=y3;
				    fa[x3]=y1;
		    	}
		    }
		}
	}
	printf("%d",cnt);
	return 0;
} 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值