D30 基环树 遍历最小字典序 P5022 [NOIP2018 提高组] 旅行

视频链接:D30 基环树 P5022 [NOIP2018 提高组] 旅行_哔哩哔哩_bilibili

P5022 [NOIP 2018 提高组] 旅行 - 洛谷

基环树的遍历最小字典序问题:出边排序。暴力断边。剪枝优化。

// 基环树 遍历最小字典序 暴力 O(n^2)
#include<bits/stdc++.h>
using namespace std;

const int N=5010;
int n,m,a,b;
vector<int> e[N];
pair<int,int> edge[N];
int du,dv,vis[N],cnt,better;
vector<int> path(N,N);

void dfs1(int x){ //树搜索
  vis[x]=1;
  path[cnt++]=x;
  for(int y:e[x])if(!vis[y]) dfs1(y);
}
bool dfs2(int x){
  if(!better){
    //剪枝:若序号变大则回退,变小则走完
    if(x>path[cnt]) return 1;
    if(x<path[cnt]) better=1;
  }
  vis[x]=1;
  path[cnt++]=x;
  for(int y:e[x]){
    if(vis[y])continue;
    if(y==du&&x==dv)continue;
    if(y==dv&&x==du)continue;
    if(dfs2(y)) return 1;
  }
  return 0;
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++){
    scanf("%d%d",&a,&b);
    e[a].push_back(b);
    e[b].push_back(a);
    edge[i]={a,b};
  }
  for(int i=1;i<=n;i++)sort(e[i].begin(),e[i].end()); //出边排序
    
  if(m==n-1) dfs1(1);
  else{
    for(int i=1;i<=m;i++){ //暴力断边
      du=edge[i].first;
      dv=edge[i].second;
      memset(vis,0,sizeof vis);
      cnt=better=0;
      dfs2(1);
    }
  }
  for(int i=0;i<n;i++)printf("%d ",path[i]);
}

 

P5049 [NOIP 2018 提高组] 旅行 加强版 - 洛谷

// 基环树 遍历最小字典序 O(nlogn)
#include<bits/stdc++.h>
using namespace std;

const int N=500010;
int n,m,vis[N],path[N],fa[N],inc[N],yzd,cnt,tmp,yhs;
vector<int> e[N];

void dfs1(int x){ //树搜索
  vis[x]=1;
  path[++cnt]=x;
  for(int y:e[x])if(!vis[y]) dfs1(y);
}
void find(int x,int f){ //找环
  if(yzd) return;
  if(fa[x]){
    while(f!=x){
      inc[f]=1;
      f=fa[f];
    }
    inc[x]=1; //在环上
    yzd=1;    //已找到
    return;
  }
  fa[x]=f;
  for(int y:e[x]) if(y!=f) find(y,x);
}
void dfs2(int x){ //环树搜索
  vis[x]=1;
  path[++cnt]=x;
  if(inc[x]){ //x在环上
    int flag=0;
    for(int i=0;i<e[x].size();i++){ //枚举x的出边点
      if(yhs) break;
      int y=e[x][i];
      if(vis[y]) continue;
      if(inc[y]){ //y在环上
        i++;
        if(vis[e[x][i]]) i++; //跳过环的入口点
        if(i<e[x].size()) tmp=e[x][i]; //环点x的未访问的第一个出边点
        else if(y>tmp){ //环点y大于前面环点x的出边点,该回去访问了
          flag=1; //找到回溯点
          yhs=1;  //已回溯,环上仅回溯这一次
        }
        break;
      }
    }
    for(int y:e[x]){
      if(vis[y]) continue;
      if(inc[y]&&flag) continue; //不访问环点y,从此处回溯
      dfs2(y);
    }
  }
  else{ //x在环外
    for(int y:e[x])if(!vis[y]) dfs2(y);
  }
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i=1,u,v; i<=m; i++){
    scanf("%d%d",&u,&v);
    e[u].emplace_back(v);
    e[v].emplace_back(u);
  }
  for(int i=1;i<=n;i++) sort(e[i].begin(),e[i].end()); //出边排序
  
  if(m==n-1) dfs1(1);
  else{
    find(1,1);
    tmp=0x3f3f3f3f;
    dfs2(1);
  }
  for(int i=1; i<=n; i++) printf("%d ",path[i]);
}

 

浅谈基环树 - 洛谷专栏

 

posted @ 2022-07-11 21:55  董晓  阅读(423)  评论(0)    收藏  举报