题解 回家
考场上数组开小了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;
}