首页 > 其他分享 >【基环树 | 题解】P5022 [NOIP2018 提高组] 旅行

【基环树 | 题解】P5022 [NOIP2018 提高组] 旅行

时间:2023-10-05 17:22:43浏览次数:44  
标签:nxt qu 环上 vis int 题解 NOIP2018 基环树

前言

一日知基环树弱,固补题。

关于基环树

基环树定义

一个环,环上每个点都有一颗以该点为根的树,如下图为一棵基环树

基环树例图

关于基环树常规思路

通常来说基环树常规思路是先处理环上树的结果,后通过树的结果来处理换上结果

具体处理方式依照题目来定。

然而只是通常来说

因为基环树的问题灵活性强且就算没专门学过基环树的也能做出基环的题,所以 OIwiki 也没有该算法的具体讲解。

典题

洛谷P5022 [NOIP2018 提高组] 旅行

题目描述

给定一个 \(n\) 个点 \(m(1\le m\le n)\) 条边的无向图,并保证该图联通。

选定一个出发,当走到 \(s\) 时,可执行两种操作:

  • 规则1 走向与 \(s\) 相连且未经过的点
  • 规则2 定义 \(u\) 走向的 \(s\),走向 \(u\) 。(口语化表述即为退回)

每当第一次经历一个点 \(s\) ,将 \(s\) 放入序列 \(ans\) ,需要保证经过每一个点。

求对于该图字典序最小的 \(ans\) 。

输入格式

输入共 \(m + 1\) 行。第一行包含两个整数 \(n,m(m ≤ n)\),中间用一个空格分隔。

接下来 m 行,每行包含两个整数 \(u,v (1 ≤ u,v ≤ n)\) ,表示编号为 \(u\) 和 \(v\) 的点之间有一条边,两个整数之间用一个空格分隔。

输出格式

输出包含一行,\(n\) 个整数,表示字典序最小的序列。相邻两个整数之间用一个空格分隔。

数据规模与约定

对于 \(100\%\) 的数据和所有样例, $1 \le n \le 5000 $ 且 \(m = n ? 1\) 或 \(m = n\) 。

Solution

思路

对于 \(n-1\) 条边的联通无向图,显然是一棵树。此时只需要从 \(1\) 出发,每次贪心深搜即可。

对于 \(n\) 条边的连通无向图,我们发现恰好符合基环树的性质。但无论 \(1\) 是否在环上,我们都需要从 \(1\) 点出发保证答案字典序最小。

首先考虑 \(1\) 在环上的情况。通过题目给出的两个规则,我们可以发现,从 \(1\) 出发,若走到环上一个点后按照环的原路返回到点 \(1\) ,则点 \(1\) 无法再次向走向刚才走过的那些点。

所以环上走的方案只有两种可能,如下:

`- 绕着环走一次

情况1`

  • 绕着环走的过程中返回 \(1\),并往另一个方向走。

情况2

实际上,这两种方案可以抽象成一种情况,第一种情况相当于绕一圈再返回起点,即为第二种的特殊情况。

也就说,我们当前需要解决的就是判断何时返回。根据题目规则2,显然能够发现,在反向走环过程中,我们必须要将 以环上点为根节点 的树全部走完。

考虑贪心,当我们正向走环的时候,当走到点 \(u\) ,我们显然可以求出从 \(u\) 开始反向走边字典序最小的下一个新遇到的点 \(v\) 。

  • 若走到 \(u\) 点的过程中环上的所有树均已走过,则 \(v\) 为 \(1\) 反向的第一个点。
  • 若走到 \(u\) 点的过程中环上有树未走完,则 \(v\) 为距离 \(u\) 最近的一颗未走完的树上最小的未走过的子节点。

此时,若 \(v\) 小于 \(u\) 正向走到的下一个环上的点,则开始返回。然后就能得到字典序最小的序列了。

然后考虑 \(1\) 不在环上的情况。

为了字典序最小,显然还是要从 \(1\) 开始走,先按照走树的贪心策略走,若走的过程中进入环,则将第一次进入环的点作为起始点,无视,该点与父节点连的边,按照环的处理方式处理该环即可。

具体细节代码中有具体注释。

代码

#include<bits/stdc++.h>
using namespace std;
#define Pair pair<int,int>
#define mk make_pair
const int N=5e3+7;
int n,m;
vector<int> nxt[N];
bool vis[N],flt[N];
bool flag=false;
queue<int> ans;
void cir(int it,int fa,int be){
    vis[it]=true;
    for(int v:nxt[it]){
        if(v==fa) continue;
        if(v==be) flag=true;
        if(vis[v]) continue;
        cir(v,it,be);
    }
}
void find_circle(){
    for(int i=1;i<=n;++i){
        flag=false;
        memset(vis,0,sizeof(vis));
        cir(i,0,i);
        if(flag){
            flt[i]=true;
        }
    }
}

bool ret=false;//记录是否回退
void dfs(int it,int fa,int minn){
    cout<<it<<" ";
    priority_queue<int,vector<int>,greater<int> > qu;
    vis[it]=true;
    ans.push(it);
    //init
    int maxt=0,mint=0x3f3f3f3f;
    for(int v:nxt[it]){
        if(vis[v]) continue;
        qu.push(v);
        if(flt[v]){
            mint=min(mint,v);
            maxt=max(maxt,v);
        }
    }
    //非环
    if(!flt[it]){
        while(!qu.empty()){
            dfs(qu.top(),it,minn);
            qu.pop();
        }
    }
    else{//环
        int xtt=minn;
        if(!xtt&&maxt!=mint) xtt=maxt;//记录环的另一端,注意若1在环中会导致maxt=mint然后莫名其妙回溯导致23wa
        while(!qu.empty()){
            int nxt=qu.top();
            qu.pop();
            if(vis[nxt]) continue;//防止再次走环的另一端
            if(flt[nxt]){//绕着环走
                if(flt[fa]&&xtt<nxt&&!ret&&qu.empty()){//若可回溯支链(或环的另一端)最小小于下一个环的点且曾经没回退过并且当前无支链小于绕环,则可以退回
                    ret=true;
                    continue;//跳过并走完支链后方可回溯
                }
                // cout<<it<<"->"<<nxt<<endl;
                dfs(nxt,it,(!qu.empty() ? qu.top():xtt));//选择绕环并去传递可回溯支链的最小
            }
            else{//走支链
                // cout<<it<<"->"<<nxt<<endl;
                dfs(nxt,it,minn);
            }
        }
    }
}
void solve(){
    memset(vis,0,sizeof(vis));
    dfs(1,0,0);
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=m;++i){
        int u,v;
        cin>>u>>v;
        nxt[u].push_back(v);
        nxt[v].push_back(u);
    }
    find_circle();
    solve();
    return 0;
}

标签:nxt,qu,环上,vis,int,题解,NOIP2018,基环树
From: https://www.cnblogs.com/I-am-yzh/p/17743650.html

相关文章

  • CF1249(Div. 3) 题解(A to D)
    \(\texttt{EF}\)忘(不)记(会)写了。AYetAnotherDividingintoTeams题解题目大意有一个长度为\(n\)的序列\(a_1,a_2,\cdots,a_n\),将他们分为若干组,使得每一组没有两个数的差为\(1\),使分的组数尽可能少。解题思路排完序后对于任意\(1\lei<n\)均有\(a_i\)与\(a_{i......
  • Luogu CF1174C 题解
    这道题其实不难。\(\gcd(i,j)=1\),其实就是\(i\)与\(j\)互质。如果\(i\)与\(j\)不互质,那么我们一定要让\(a_i\)与\(a_j\)相同,只有这样,才能使\(a\)序列中的最大值最小化。所以,我们可以使用埃氏筛法,当筛到质数时,给它和它的倍数都赋值为一个新数。ACCode:#include......
  • Luogu P8651 题解
    这是让我最崩溃的一道橙题了。整整11次提交才AC。这道题有几个要点必须注意:判断日期是否合理。按顺序输出。判断重复的日期。首先,我们来看怎么判断日期是否合理。我们知道大月有\(31\)天,小月有\(30\)天,二月看平年闰年。所以,我们可以写出这样的代码:boolc......
  • Luogu CF1469B 题解
    这道题其实并不难。题目大意是这样的:已知两个序列\(r\)和\(b\),求出合并后的最大前缀和。很好发现:答案就是\(r\)和\(b\)各自的最大前缀和之和。但要注意:\(r\)和\(b\)可以什么都不取,因此\(maxa\)和\(maxb\)初始要赋值为\(0\)。ACCode:#include<iostream>using......
  • Luogu CF755B 题解
    这题其实不难。两人最佳的方案就是先说对方会的词。不难证明,设先手会说\(n\)个单词,后手会说\(m\)个单词,若\(n>m\),则先手胜,若\(n<m\),则后手胜。那如果\(n=m\)呢?假设两人都会说的单词数为\(k\),那么一番推理发现,当两人说了\(k-1\)次,就没有两人都会的词了,可以回到之......
  • Luogu CF1133B 题解
    这道题其实很简单要让两数和为\(k\)的倍数,需要满足以下两条件之一:两数都是\(k\)的倍数两数的余数和为\(k\)因此,我们可以先统计出余数再按上述条件算出共有多少组,即可得到答案注意:当\(k\)为偶数时,余数为\(k/2\)的数要两两配对,不要多算这里统计的是组数,......
  • Luogu P7627 题解
    这题其实不难但如果用暴力,肯定过不了所以我们得想另一种办法我们发现,只有\(1\)异或\(0\)的值为\(1\)例如:\(1\),\(0\),\(1\)两两异或的和为2其实就是每个\(0\)与每一个\(1\)异或时,\(sum\)要加\(1\)所以,我们只要把每一位的\(0\)和\(1\)的数量都统计出来......
  • Luogu CF400C 题解
    这道题其实不难,只是一道非常简单的模拟题。我们发现,每顺时针转动\(4\)次、镜面对称\(2\)次、逆时针旋转\(4\)次,就变回原来的样子了。所以,在操作前,我们可以让\(x\getsx\bmod4\),\(y\getsy\bmod2\),\(z\getsz\bmod4\)。接下来,只需在草稿纸上画一画,即可知道顺时针转一次,一......
  • CodeForces 814E An unavoidable detour for home 题解
    更好的阅读体验题意题目链接(洛谷翻译)给出\(n\)个点,和每个点的度\(d_i\)让你构造出一张无向图满足以下两条性质:点\(1\)到点\(i\)仅有唯一一条最短路。点\(1\)到点\(i\)的最短路长度大于等于点\(1\)到点\(i-1\)的最短路长度。求能构成满足条件的无向图......
  • [题解] CF474E Pillars
    题意给定长度为\(n\)的序列\(a\)和常数\(d\),输出一个最长的\(a\)的子序列,使得相邻两项的差的绝对值大于等于\(d\)。\(n\le10^5\)题解数据结构优化DP的板子题了吧。首先,这道题看上去就很LIS,我们尝试着用类似LIS的思路去做。设\(f_i\)表示以\(i\)结尾的符合......