P5049 【旅行(数据加强版)】

The_World_exe

2020-10-21 16:46:36

Solution

# [P5049 旅行(数据加强版)](https://www.luogu.com.cn/problem/P5049) 这题目真的是细节超多,乍一看感觉挺简单,其实想要AC并不容易 + 在存图的时候可以采用$\text{vector}$邻接表的方式储存第$x$号点的第$i$条出边$e[x][i]$,存完后对于每一组$e[x]$按照从小到大的顺序$\text{sort}$一下,保证【编号小的边指向的点的编号】小于【编号大的边指向的点的编号】 + 之后用$\text{tarjan}$算法将整张图处理一遍,用$loop[]$数组记录一下所有在环里的点,其中$loop[i]=1$表示$i$号点在环上。在用$\text{tarjan}$算法处理整张图的过程中,用变量$bottom$表示按照顺序进行$\text{dfs}$的第一个在环中的点的编号,如下图中$bottom=7$。在之后的$\text{dfs}$过程中,用$already$记录是否已经回溯过。 ![图1](https://s1.ax1x.com/2020/10/21/BCRmfx.png) + $\text{dfs}$过程中,对于当前点$x$,分成两种情况 1. 当前$\text{dfs}$到的点$x$不在一个环上 2. 当前$\text{dfs}$到的点$x$在一个环上 + 对于第一种情况,直接按照编号从小到大的顺序枚举$x$点的出边。 + 对于第二种情况,就复杂多了 1. 对于$bottom=7$,因为是$dfs$到的第一个点,故不存在回溯操作。但是该点一定存在两个在环上的且与之相连的点$4、5$,将第二小的且在环上的点$5$记为$mn$,再$\text{dfs}$最小的点$4$。 ```c++ if(x==bottom) { for(int i=0;i<e[x].size();i++) { int to=e[x][i]; if(vis[to]==1)continue; if(loop[to]==1) { kk=to; int tmp=i+1; mn=e[x][tmp]; break; } } } ``` 2. 当后续$\text{dfs}$到环上的点时,如果有一个点$x$的一条出边$i$所指向的点$to$满足以下条件则回溯: 1. $loop[to]=1$,即$to$在环上 2. $i+1=e[x].size()$,即当前出边$i$是$x$点的最后一条**有效**边($to$是与$x$直接相连的编号最大的点)(若当前出边$i$是$x$点的倒数第二条出边,但最后一条出边指向的是上一个访问的节点满足该条件) 3. $mn<to$,即之前找到的$mn$的编号比$to$的字典序小 4. $already=0$,即之前从未回溯过 若以上条件均满足,则标记$already=1$,之后直接$\text{dfs}(mn,x)$就好了 上图的例子中不存在能回溯的情况,但是样例里有qwq ```c++ if(already==0&&mn<to&&loop[to]==1&&(i+1==e[x].size()||(i+2==e[x].size()&&e[x][i+1]==from)))//若当前出边i是x点的倒数第二条出边,但最后一条出边指向的是上一个访问的节点满足该条件 { int to=mn; already=1; dfs(to,x); } ``` 3. 当$already=1$,代表早就已经回溯过了,不能再回溯了,故直接$\text{dfs}(to,x)$ ```c++ if(already==1) dfs(to,x); ``` 4. 当$loop[to]=1且already=0$但是又不能回溯,则说明 + $x$上可能有1条或多条支链不在环上,之后的点想要回溯只能回溯到这一条有支链的点上,否则这条支链上的点就再也无法经过了。故需要更新$mn$的值:$mn=e[x][i+1]$,即在$x$的出点中选择恰好比$to$大的点作为$mn$的值。 + 但是需要排除$x$上没有支链但是$mn>to$的情况, 1. 在上图中,当$x=4,to=6$时,则更新$mn$的值,$mn=9$; 2. 当$x=6,to=8$,但是$mn=9>to$时,需要判断$[e][x][i+1]$是否为0,若$e[x][i+1]=0$说明$x$没有支链,则无需更新$mn$的值。 ```c++ else if(loop[to]==1) { int tmp=i+1; while(vis[e[x][tmp]]==1)tmp++;//避免选中已经dfs过的点 if(e[x][tmp]!=0)mn=e[x][tmp];//排除只有一条 dfs(to,x); } ``` 大概就是这么多啦,下面再贴一份AC代码 ```c++ #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<vector> #include<stack> using namespace std; const int N=5e5+5; int n,m; int dfn[N],low[N],vis[N]; int loop[N],bottom,mn=0x3f3f3f3f; bool looped,already; int kk; vector<int>e[N]; stack<int>s; bool cmp(int x,int y) { return x<y; } void tarjan(int x,int from,int cnt) { dfn[x]=cnt; low[x]=cnt; s.push(x); vis[x]=1; for(int i=0;i<e[x].size();i++) { int to=e[x][i]; if(to==from)continue; if(vis[to]==1) { low[x]=min(low[x],dfn[to]); looped=1; bottom=to; } else { tarjan(to,x,cnt+1); low[x]=min(low[to],low[x]); } if(looped==1)return; } if(dfn[x]==low[x]) { s.pop(); vis[x]=0; } } void get_loop() { int x; while(x!=bottom&&s.empty()!=1) { x=s.top(); s.pop(); loop[x]=1; } loop[bottom]=1; } //以下是核心部分******** void dfs(int x,int from) { bool in_loop=loop[x]; cout<<x<<" "; vis[x]=1; if(in_loop==0||already==1) { for(int i=0;i<e[x].size();i++) if(vis[e[x][i]]==0)dfs(e[x][i],x); } else if(in_loop==1) { if(x==bottom) { for(int i=0;i<e[x].size();i++) { int to=e[x][i]; if(vis[to]==1)continue; if(loop[to]==1) { kk=to; int tmp=i+1; mn=e[x][tmp]; break; } } } for(int i=0;i<e[x].size();i++) { int to=e[x][i]; if(vis[to]==1)continue; if(already==0&&mn<to&&loop[to]==1&&(i+1==e[x].size()||(i+2==e[x].size()&&e[x][i+1]==from))) { int to=mn; already=1; dfs(to,x); } else if(already==1) dfs(to,x); else if(loop[to]==1) { int tmp=i+1; while(vis[e[x][tmp]]==1)tmp++; if(e[x][tmp]!=0)mn=e[x][tmp]; dfs(to,x); } else dfs(to,x); } } } //以上是核心部分******** int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); e[u].push_back(v); e[v].push_back(u); } for(int i=1;i<=n;i++)sort(e[i].begin(),e[i].end(),cmp); tarjan(1,0,1); get_loop(); memset(vis,0,sizeof(vis)); dfs(1,1); return 0; } ``` ---- $\text{Update}\ \ 10.28$ 原代码在原题中会WA一个点,更新后修复了这个bug。感谢 @[HH_Halo ](https://www.luogu.com.cn/space/show?uid=322491)