洛谷P1197星球大战题解

题解之前的小故事—— 细 节 决 定 成 败 细节决定成败
两天前为了练习并查集,在洛谷做了这道题,刚开始的时候没有思路就去看了题解,找到解题方向后就去自己写了代码,在本地通过样例后就去提交,结果只通过了四个点,就回去检查自己的代码,发现没有什么大问题,就去看了题解的代码,发现解题思想是一致的,就又交了一发,结果还是死活WA了六个点只有40分,又回去检查代码,发现真的没有什么问题啊,我想是不是测试点改了数据,可是这道题恰恰不能下载测试点的数据,就拿题解的代码去提交,结果题解的代码能过。这就难到我了,没有数据可测的,自己检查又检查不出来,一下午就在找bug过程中度过,结果还是没找出来。我发现这种滋味真的不好受,晚上去上课,心里也是痒痒的。第二天来了之后还是不想放弃,又想到是不是洛谷判题机有问题,拿到其他OJ上去提交也是只过了四个点,又重复了昨天的一波发愣后,我实在是有点失落了,叫上实验室的小伙伴看了之后,他也没有发现哪里出错了,我就有点想放弃了。下午去做别的事情没管这道题,但我心里想谁要是帮我找到这个bug,我一定要请他吃顿饭。
这就是这篇题解今天才来写的原因,为了兑现两天前的承诺(只可惜bug是我自己今天又在琢磨这道题时查看别人提交的代码时发现的),我晚上要好好犒劳自己(滑稽
——————————分割线
回归正题,写这篇博客就是为了提醒自己写代码时一定要细心谨慎,不该偷的懒一定不能偷!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
基本思路:这个题是一个倒过来的并查集。题目的目的是求各个时间联通块的个数,从最后时刻往前做,并查集的初始状态即被破坏后的图。
主要思想是,并查集中处于一个联通块的祖先结点相等,当任意两个祖先不同的联通块联通时,联通块会减少一个(同时当破坏的被恢复时会增加一个),通过这种操作可以知道目前有多少个联通块。而本题只需要知道是否在一个并查集中就可知道是否增删联通块了,因此核心算法不是很难。
具体操作说明附在代码里
看我40分的代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=400010;
int n,m,k,cnt,num,fa[maxn],head[maxn],broken[maxn],ans[maxn];
bool bro[maxn];

struct node{
	int from,to,next;
}e[maxn];

void add(int a,int b){ //链式前向星
	e[++cnt].from=a;
	e[cnt].to=b;
	e[cnt].next=head[a];
	head[a]=cnt;
}

int find(int x){ //寻找祖先
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}

void unite(int x,int y){ //合并两个连通块
	fa[find(x)]=find(y);
}

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		fa[i]=i;
	}
	while(m--){
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);  //无向图要加两次
	}
	cin>>k;
	for(int i=1;i<=k;i++){ //记录被摧毁的点
		cin>>broken[i]; 
		bro[broken[i]]=true;
	}
	num=n-k; //计算剩余没有被摧毁的点,就有多少个连通块
	for(int i=1;i<=2*m;i++){ //一共有2*m条边
		if(!bro[e[i].from]&&!bro[e[i].to]&&find(e[i].from)!=find(e[i].to)){ //如果没有被摧毁且不在一个连通块里
			unite(e[i].to,e[i].from); //合并
			num--; //连通块减一
		}
	}
	ans[k+1]=num; //被摧毁后实际的连通块个数
	for(int i=k;i;i--){ //逆序,模拟修复过程
		num++; //修复了一个点,就加了一个连通块
		bro[broken[i]]=false; //标记被修复了
		for(int j=head[broken[i]];j;j=e[j].next){
			if(!bro[e[j].to]&&find(broken[i])!=find(e[j].to)){ //如果和已经修复好的连通块存在边
				unite(e[j].to,broken[i]); //合并
				num--; //连通块减一
			}
		}
		ans[i]=num; //记录修复了这个点后实际的连通块个数
	}
	for(int i=1;i<=k+1;i++){ //逆序输出
		cout<<ans[i]<<endl;
	}
	return 0;
}

提交结果:
在这里插入图片描述
我这两天一共提交的(除了最后一次,中间两次AC的是复制的题解的代码):
在这里插入图片描述
惨兮兮的,直到我刚才去这道题所有的提交记录里去看别人的代码,才终于发现我哪里错了。
最后AC的代码(和上面的基本一样,只有一个细节也是最致命的细节有点改动,希望读者们以后写代码的时候也一定要注意细节,该偷懒的时候可以偷懒,但千万也要思考偷懒会不会带来不好的后果,有点像是在谈人生的问题 ):

#include <bits/stdc++.h>
using namespace std;
const int maxn=400010;
int n,m,k,cnt,num,fa[maxn],head[maxn],broken[maxn],ans[maxn];
bool bro[maxn];

struct node{
	int from,to,next;
}e[maxn];

void add(int a,int b){
	e[++cnt].from=a;
	e[cnt].to=b;
	e[cnt].next=head[a];
	head[a]=cnt;
}

int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}

void unite(int x,int y){
	fa[find(x)]=find(y);
}

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		fa[i]=i;
	}
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	cin>>k;
	for(int i=1;i<=k;i++){
		cin>>broken[i];
		bro[broken[i]]=true;
	}
	num=n-k;
	for(int i=1;i<=2*m;i++){
		if(!bro[e[i].from]&&!bro[e[i].to]&&find(e[i].from)!=find(e[i].to)){
			unite(e[i].to,e[i].from);
			num--;
		}
	}
	ans[k+1]=num;
	for(int i=k;i;i--){
		num++;
		bro[broken[i]]=false;
		for(int j=head[broken[i]];j;j=e[j].next){
			if(!bro[e[j].to]&&find(broken[i])!=find(e[j].to)){
				unite(e[j].to,broken[i]);
				num--;
			}
		}
		ans[i]=num;
	}
	for(int i=1;i<=k+1;i++){
		cout<<ans[i]<<endl;
	}
	return 0;
}

d e t a i l s    m a k e    a    d i f f e r e n c e details \;make \;a \;difference detailsmakeadifference

最后祝大家生活愉快!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值