八中教室的灯加强版

Description

八中一共有被用M条双向道路连接的N个教室(1<=N,M<=3000)。为了关闭整个八中,master wen 计划每一次关
闭掉一个教室。当一个教室被关闭了,所有的连接到这个教室的道路都会被关闭,而且再也不能够被使用。master
wen现在正感兴趣于知道在每一个时间(这里的“时间”指在每一次关闭教室之前的时间)时他的八中是否是“全
连通的”——也就是说从任意的一个开着的教室开始,能够到达另外的一个教室。注意自从某一个时间之后,可能
整个八中都开始不会是“全连通的”。

Input

第一行给出数字N,M,代表有N个教室,M条边,1<=N,M<=200000
接下来N行来用描述教室之间相连的情况
接下来N行,每行给出一个数字,代表关闭了哪个教室的灯

Output

输出N行,每行输出"YES"或"NO".
第一行输出最开始时整个八中是不是连通的
后面的N-1用来描述关闭某个教室的灯后,八中是不是连通的。

Sample Input

4 3
1 2
2 3
3 4
3
4
1
2

Sample Output

YES
NO
YES
YES

题目大意:
给你n个点和m条边,每次删掉一个点(被删的点就永久消失),并且与这个点相连的边也被删掉,现求删除这个点后,场上的所有点是否还能连通。

题目解析:
如果要边输入边删点的话,那么肯定要构造一棵树,并且还要找删点后一个还未删的点去进行搜索,看整个图是否连通,如果数据小还好,但如果数据大的话,肯定会超时。
那么我们换一种思路,既然是看整个图是否连通,那么是不是看每一个的根结点是否一致,那么最好的方法就是使用并查集,为了使时间复杂度更小,我们可以使之扁平化(但好像正着搜没有什么作用,因为每次是删头结点,并不是连接两点),但若使用并查集的话,每次搜索还是要搜寻每个点的根节点,并且还要判断每一条边能不能走,这也一定会超时,那如果每次不遍历每一个点,那是否就能不超时呢?
其实要做到这一点也不难,我们不如反向思维,是不是从开头每次删一个点直至删完,就相当于从后面做每次加一个点直至补完,并且从结尾往前加点,那么每次符合条件的边就不可能变动(就不用考虑之前连接的两点是否连通),并且每一个节点的根就只会连到另一个节点上,扁平化就更能体现作用。
但现在还是没有解决遍历点的问题及遍历边的问题(重中之重,打起精神来,注意!)
遍历点的问题:是不是每加一个点,就相当于多了一个连通块,而每把两个根节点不相同的连通块相连就少了一个根节点,若要使整个图为一个环形,则整个图为一个连通块。
遍历边的问题:既然点是按先后顺序出现的,而边的出现是看两头的点是否出现,若按平常的话,一定要去搜索,在这里我们只需排序即可。我们先记录下每一个点出现的先后排名,然后记录下每一条边的两个端点,最后按照每条边的端点出现的先后排序即可(记得按每条边的最晚出现的点与对方做比较)。我们回到程序中,在搜的时候只要看当前的边是否符合条件,若符合,则把条的两端点的根相连,若不符合,则不往下继续搜。

代码实现:

#include<bits/stdc++.h>
using namespace std;
int f[300000],xx,yy,n,m;
int a[300000],l[200001];//a数组记录每个点,l数组记录点的出现顺序
bool p[200001],kk[200001];//p数组记录点是否出现,kk数组记录是否能连通
struct dd {
	int x,y;
} v[200100];//dd结构体记录每一条边的两个点
bool cmp(dd a,dd b) {
	return max(l[a.x],l[a.y])<max(l[b.x],l[b.y]);//把两条边的端点按出现的顺序排序
}
int find(int i) {
	if(f[i]==i) return i;
	return f[i]=find(f[i]);//并查集用扁平化
}
int main() {
	int i,j,k,tot=1,ans=0;
	scanf("%d%d",&n,&m);
	for(i=1; i<=m; i++)
		scanf("%d%d",&v[i].x,&v[i].y);
	for(int i=n; i>=1; i--) {
		scanf("%d",&a[i]);
		l[a[i]]=i;
	}
	kk[1]=1;
	sort(v+1,v+m+1,cmp);//将每一条边进行排序
	p[a[1]]=1;//由于删点并不包括最后一个点,所以第一个点定义为已经出现
	ans=1;//已经有一个连通块
	for(int i=1; i<=n; i++)
		f[i]=i;//初始化
	for(i=2; i<=n; i++) {
		ans++;//每增加一个点,则多了一个连通块
		p[a[i]]=1;//把当前点进行标记
		while(tot<=m&&p[v[tot].x]==1&&p[v[tot].y]==1) {//判断两点是否已出现
			if(find(v[tot].x)!=find(v[tot].y)) {//若两点连通块的根不相同,则相连
				f[find(v[tot].x)]=find(v[tot].y);//进行连接
				ans--;//连通块数量减一
			}
			tot++;//继续查找
		}
		if(ans==1) kk[i]=1;//判断是否连通
	}
	for(int i=n; i>=1; i--)
		if(kk[i]==1)
			printf("YES\n");
		else
			printf("NO\n");//打印答案
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值