题解 回家

传送门

考场上数组开小了90pts爆成60pts

考虑求出点双,缩点后重新建边,则新图一定形成一棵树
dfs跑一遍,从n点回溯时记录经过的必经点就行
但是有个坑点:一个点可能不止属于一个点双,所以重新连边时不能用并查集判断两点是否属于同一点双
所以我改题是就又哈希又动态数组乱搞
其实不用那么麻烦,有结论:只有割点可能同时属于多个点双
那建边时把割点孤立出来,建立一张只有割点和缩点后点双的图就好了
而且这样建完图后一定是一棵树,dfs跑就好了
vcc,dcc的板子建议再背一遍
tarjan就是「一算有八板,八板各不同」@Yubai

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 500010
#define ll long long 
#define ld long double
#define usd unsigned
#define ull unsigned long long
//#define int long long 

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m;
int head[N], size, dfn[N], low[N], dep, now, h[N<<2], size2, sta[N], top, lim;
int bel[N<<2], lookup[N<<2];
bool cut[N<<2], con[N<<2], searched[N<<2];
vector<int> dcc[N];
struct edge{int from, to, next;}e[N<<2];
struct edge2{int to, next;}e2[N<<2];
inline void add(int s, int t) {edge *k=&e[++size]; k->from=s; k->to=t; k->next=head[s]; head[s]=size;}
inline void add2(int s, int t) {edge2 *k=&e2[++size2]; k->to=t; k->next=h[s]; h[s]=size2;}

void tarjan(int u, int fat) {
	//cout<<"tarjan "<<u<<endl;
	dfn[u]=low[u]=++dep;
	sta[++top]=u;
	int cnt=0;
	for (int i=head[u],v; i; i=e[i].next) {
		v = e[i].to;
		if (!dfn[v]) {
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (dfn[u]<=low[v]) {
				 if (i!=1 || ++cnt>1) cut[u]=1;
				 ++now;
				 int t;
				 do {
				 	t=sta[top--];
				 	dcc[now].push_back(t); //, cout<<now<<' '<<t<<endl;
				 	bel[t]=now;
				 } while (t!=v) ;
				 dcc[now].push_back(u);
			}
		}
		else low[u]=min(low[u], dfn[v]);
	}
}

void dfs(int u) {
	//cout<<"dfs "<<u<<endl;
	searched[u]=1;
	if (u==bel[n]) {con[u]=1; return ;}
	for (int i=h[u],v; i; i=e2[i].next) {
		v = e2[i].to;
		//cout<<v<<endl;
		if (!searched[v]) {
			dfs(v);
			if (con[v]) {
				con[u]=1;
				if (u>lim && u!=bel[1]) sta[++top]=lookup[u];
			}
		}
	}
}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	int T;
	
	T=read();
	while (T--) {
		n=read(); m=read();
		size=1; dep=0; now=0; size2=1; top=0;
		memset(head, 0, sizeof(int)*(n+10));
		memset(dfn, 0, sizeof(int)*(n+10));
		memset(low, 0, sizeof(int)*(n+10));
		memset(cut, 0, sizeof(int)*(n+10));
		memset(h, 0, sizeof(h));
		memset(con, 0, sizeof(int)*(n+10));
		memset(searched, 0, sizeof(int)*(n+10));
		memset(bel, 0, sizeof(int)*(n+10));
		memset(lookup, 0, sizeof(lookup));
		for (int i=1; i<=n; ++i) dcc[i].clear();
		for (int i=1,u,v; i<=m; ++i) {u=read(); v=read(); if (u==v) continue; add(u, v); add(v, u);}
		
		tarjan(1, 0);
		top=0;
		
		#if 0
		cout<<"cut: "; for (int i=2; i<=size; ++i) cout<<cut[i]<<' '; cout<<endl;
		cout<<"dfn: "; for (int i=1; i<=n; ++i) cout<<dfn[i]<<' '; cout<<endl;
		cout<<"low: "; for (int i=1; i<=n; ++i) cout<<low[i]<<' '; cout<<endl;
		#endif
		lim = now;
		for (int i=1; i<=n; ++i) if (cut[i]) bel[i]=++now, lookup[now]=i;
		for (int i=1; i<=now; ++i) 
			for (int j=0,t; j<dcc[i].size(); ++j) {
				t = dcc[i][j];
				if (cut[t]) add2(i, bel[t]), add2(bel[t], i); //, cout<<"add "<<i<<' '<<bel[t]<<endl;
			}
		//cout<<lim<<' '<<now<<' '<<size<<' '<<size2<<endl;
		dfs(bel[1]);
		//for (int i=1; i<=top; ++i) cout<<sta[i]<<' '; cout<<endl;
		sort(sta+1, sta+top+1);
		//top = unique(sta+1, sta+top+1)-sta-1;
		printf("%d\n", top);
		for (int i=1; i<=top; ++i) printf("%d ", sta[i]);
		printf("\n");
	}

	return 0;
}

但是还有另一种更简单的做法:
我们缩点是为了使图形成一棵树,方便dfs回溯找路径上的必经点
但我们要找的实际上是1-n路径上的必经点,
而一个点必经的充分必要条件是它在这条路径上且它是割点
那就没有必要缩点了,先tarjan预处理出dfn[\ ]和low[\ ],同时记录下每个点在搜索树上的父亲,
然后直接从n回溯到1节点,同时判断下经过的每个点是不是割点就好
搜索树的构建顺序无关紧要,因为如果一个点在路径上且是割点,那无论搜索树怎么构建它都是必经的

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 200100
#define ll long long 
#define ld long double
#define usd unsigned
#define ull unsigned long long
//#define int long long 

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m;
int head[N], size, dfn[N], low[N], dep, now, back[N], sta[N], top;
struct edge{int to, next;}e[N<<2];
inline void add(int s, int t) {edge *k=&e[++size]; k->to=t; k->next=head[s]; head[s]=size;}

void tarjan(int u, int fa) {
	//cout<<"tarjan "<<u<<endl;
	dfn[u]=low[u]=++dep;
	for (int i=head[u],v; i; i=e[i].next) {
		v = e[i].to;
		if (!dfn[v]) {
			tarjan(v, u); back[v]=u;
			low[u] = min(low[u], low[v]);
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	int T;
	
	T=read();
	while (T--) {
		n=read(); m=read();
		size=1; dep=0; now=0; top=0;
		memset(head, 0, sizeof(int)*(n+10));
		memset(dfn, 0, sizeof(int)*(n+10));
		memset(low, 0, sizeof(int)*(n+10));
		memset(back, 0, sizeof(int)*(n+10));
		for (int i=1,u,v; i<=m; ++i) {u=read(); v=read(); if (u==v) continue; add(u, v); add(v, u);}
		
		tarjan(1, 0);
		top=0;
		
		#if 0
		cout<<"dfn: "; for (int i=1; i<=n; ++i) cout<<dfn[i]<<' '; cout<<endl;
		cout<<"low: "; for (int i=1; i<=n; ++i) cout<<low[i]<<' '; cout<<endl;
		#endif
		
		for (int t=n,k; t!=1; t=k) {
			k=back[t];
			if (dfn[k]<=low[t] && k!=1) sta[++top]=k; //, cout<<k<<' '<<dfn[k]<<' '<<low[t]<<endl;
		}
		
		sort(sta+1, sta+top+1);
		printf("%d\n", top);
		for (int i=1; i<=top; ++i) printf("%d ", sta[i]);
		printf("\n");
	}

	return 0;
}
posted @ 2021-06-12 15:41  Administrator-09  阅读(14)  评论(0编辑  收藏  举报