[CSP-S模拟测试]:回家(塔尖)

题目传送门(内部题7)


输入格式

第一行一个整数$T$,表示共$T$组数据。
对于每组数据,第一行两个数$n,m$表示有$n$个建筑物,$m$条道路。
接下来$m$行,每行两个整数$u,v$,表示第$u$个建筑物和第$v$个建筑物之间相连。


输出格式

对于每组数据,输出共两行。
第一行一个数$x$表示共有$x$个必经点(不包括$1$号点和$n$号点)。
接下来一行$x$个数,描述这$x$个必经点是那些点。按从小到达输出。
注意:如果第一行输出了$0$,那么接下来你输出的第二行应该只有一个换行符。


样例

样例输入1:

1
4 3
1 2
2 3
3 4

样例输出1:

2
2 3

样例输入2:

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

样例输出2:

1
4


数据范围与提示

$T\leqslant 10$。

$n\leqslant 200,000$。

可能有重边或自环。$m\leqslant n$。


题解

到这道题,首先,应该想到割点,塔尖缩点必不可少。

但是,必经点一定是割点,但是割点不一定是必经点,因为只有在圆方树上1到n这条链上的点才是必经点。

那么我们应该怎么办呢?

显然可以塔尖缩点,建图,在新图上跑,再找回去,但是实现过于复杂,常数较大。

所以我们考虑另一种做法。

在塔尖算法中,如果一个点的时间戳小于等于它的子节点的回溯值,那它一定是一个割点。

那么,如果一个点的儿子的时间戳小于等于n的时间戳的话才是1到n的路径上的必经点。

你可能会仍给我类似下面这张图:

 

如果我们先访问了点$2$,那么点$3$和点$4$的时间戳都比点$5$小,怎么办呢?

访问了点$2$,如果我们先访问了点$3$,那么点$4$还没有被访问过,而点$4$恰恰是判定点$2$为割点的条件,而此时点$2$连割点的条件都没有满足,更不用考虑是不是必经点了。

如果我们先访问了点$4$,那么点$5$还没有被访问到,所以也不成立。


代码时刻

#include<bits/stdc++.h>
using namespace std;
struct rec
{
	int nxt;
	int to;
}e[1000000];
int head[200001],cnt;
int n,m;
int dfn[200001],low[200001],tot;
bool cut[200001];
int ans;
void pre_work()
{
	cnt=tot=ans=0;
	for(int i=1;i<=n;i++)
		head[i]=dfn[i]=low[i]=cut[i]=0;
}
void add(int x,int y)
{
	e[++cnt].nxt=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++tot;
	int flag=0;
	for(int i=head[x];i;i=e[i].nxt)
	{
		if(!dfn[e[i].to])
		{
			tarjan(e[i].to);
			low[x]=min(low[x],low[e[i].to]);
			if(dfn[x]<=low[e[i].to]&&dfn[e[i].to]<=dfn[n])//加一个判定条件
			{
				flag++;
				if(x!=1||flag>1){ans++;cut[x]=1;}
			}
		}
		else low[x]=min(low[x],dfn[e[i].to]);
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		pre_work();
		for(int i=1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add(u,v);
			add(v,u);
		}
		tarjan(1);
		printf("%d\n",ans);
		for(int i=1;i<=n;i++)
			if(cut[i])printf("%d ",i);
		puts("");
	}
	return 0;
}

不管你怎么说,反正我觉得这个代码比其他人的代码简洁多了~


rp++

posted @ 2019-07-26 09:41  HEOI-动动  阅读(217)  评论(0编辑  收藏  举报