• 竞赛题解


    (mathcal {NOIP2018} 旅行 - 竞赛题解)

    坑还得一层一层的填
    填到Day2T1了 洛谷 P5022


    题目

    (以下copy自洛谷,有删减/修改 (●ˇ∀ˇ●))

    题目描述

    小 Y 是一个爱好旅行的 OIer。她来到 X 国,打算将各个城市都玩一遍。
    小Y了解到, X国的 (n) 个城市之间有 (m) 条双向道路。每条双向道路连接两个城市。 不存在两条连接同一对城市的道路,也不存在一条连接一个城市和它本身的道路。并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。小 Y 只能通过这些 道路从一个城市前往另一个城市。
    小 Y 的旅行方案是这样的:任意选定一个城市作为起点,然后从起点开始,每次可 以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市。当小 Y 回到起点时,她可以选择结束这次旅行或 继续旅行。需要注意的是,小 Y 要求在旅行方案中,每个城市都被访问到。
    为了让自己的旅行更有意义,小 Y 决定在每到达一个新的城市(包括起点)时,将 它的编号记录下来。她知道这样会形成一个长度为 (n) 的序列。她希望这个序列的字典序 最小,你能帮帮她吗? 对于两个长度均为 (n) 的序列 (A)(B),当且仅当存在一个正整数 (x),满足以下条件时, 我们说序列 (A) 的字典序小于 (B)

    对于任意正整数 (1 ≤ i < x),序列 (A) 的第 (i) 个元素 (A_i) 和序列 BB 的第 (i) 个元素 (B_i) 相同。
    序列 (A) 的第 (x) 个元素的值小于序列 (B) 的第 (x) 个元素的值。

    输入输出格式

    输入格式:
    输入文件共 (m + 1) 行。第一行包含两个整数 (n,m(m = n 或 n+1)),中间用一个空格分隔。
    接下来 m 行,每行包含两个整数 (u,v (1 ≤ u,v ≤ n)) ,表示编号为 (u)(v) 的城市之 间有一条道路,两个整数之间用一个空格分隔。
    输出格式:
    输出文件包含一行,(n) 个整数,表示字典序最小的序列。相邻两个整数之间用一个 空格分隔。


    解析

    这道题特殊点在于它的边数 (m) 只能为 (n)(n+1),而且是一个连通图,无重边自环!这就意味着它要么是一棵树,要么是只带有一个环的“树”……这是什么“树”?——基环树(我也是第一次用这个东西QwQ)

    因为要让字典序最小,所以肯定是从城市 (1) 开始。

    首先考虑树的情况:
    贪心的,我们只要让下一步到达的点尽可能小就可以了,所以对于每一个点 (u) ,将与它相连的点按编号大小排序(即将邻接表排序),当然也可以直接用 (set) 存邻接表,反正我是这么干的。然后按照节点编号大小,走没有访问过的编号最小的节点就可以了,最后一定可以把整棵树遍历完(非常easy?)

    然后就是有一个环的情况了(考场上一直在想一次DFS (O(nlog_n)) [1]贪心……就没有想过 (O(n^2)) 也能过)。

    有一个环,但是我们最多只会走 (n-1) 条边(不能重复经过点嘛)……那把这个环拆开——删除环上的一条边,不就又变成一棵树了吗?(基环树的一般操作)
    显然我们可以一次DFS找到那一个环上的所有边((O(n))),我们把找到的环的 边的编号 存储在 (cir[]) 数组里。根据上面的思想,我们可以枚举删除 (cir[i]) ,然后按照之前树的做法贪心的求出删除 (cir[i]) 的时候的最小答案,对于每次求出的答案取最小值就可以了。
    具体怎么做?先枚举 (cir[i])(O(n))),将标记 (del) 赋值为 (cir[i]),然后从节点 1 出发遍历所有点 ((O(n))),当发现要经过的边的编号为 (del) 时,就 continue,不走该边。所以总的时间复杂度为 (O(n^2))

    额……不过STL的诡异常数好像在洛谷上有一点卡常QwQ,用set的做法在洛谷上只能开 O2 优化才能过,还是推荐直接将邻接表排序。


    源代码

    (这里还是把用set写的代码粘上来)

    /*Lucky_Glass*/
    #include<bits/stdc++.h>
    using namespace std;
    const int N=5000;
    set< pair< int,int > > lnk[N+5];
    int n,m,del;
    int ans[N+5],res[N+5]; //ans为最终答案,res为当前贪心得到的答案
    bool Prefer(){ //判断ans和res谁更优
        for(int i=1;i<=n;i++){
            if(ans[i]<res[i]) return false;
            if(ans[i]>res[i]) return true;
        }
        return false;
    }
    int vis[N+5],tag;
    void DFS(int u){
        vis[u]=tag;
        res[++res[0]]=u;
        if(res[0]==n && Prefer())
            memcpy(ans,res,sizeof res);
        for(set< pair< int,int > >::iterator it=lnk[u].begin();it!=lnk[u].end();it++){
            int v=it->first,id=it->second;
            if(vis[v]==tag || id==del) continue; //如果是访问过的点或者经过了删除了的边
            DFS(v);
        }
    }
    int cir[N+5],beg,ok;
    void Circle(int u,int pre){
        vis[u]=tag;
        for(set< pair< int,int > >::iterator it=lnk[u].begin();it!=lnk[u].end();it++){
            int v=it->first,id=it->second;
            if(v==pre || id==cir[1]) continue; //如果是父亲,或者是环的末端
            if(vis[v]==tag){
                beg=v,ok=true,cir[++cir[0]]=id; //找到环,退出
                break;
            }
            Circle(v,u);
            if(ok) cir[++cir[0]]=id; //存储环
            if(u==beg) ok=false; //完成整个环的搜索,结束存储
        }
    }
    int main(){
        ans[1]=(1<<29); //将ans的字典序初始化为最大
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            int u,v;scanf("%d%d",&u,&v);
            lnk[u].insert(make_pair(v,i)); //用pair的first存储相邻点,second存储边的编号
            lnk[v].insert(make_pair(u,i)); //这样就可以直接给邻接表排序
        }
        if(n==m){
            tag=-1; //一个简单的vis标记,只要vis[u]!=tag,那么u在这次搜索中就没有被访问过(可以减去一次memset)
            Circle(1,-1); //找环
            for(int i=1;i<=cir[0];i++){ //枚举删边
                del=cir[i];tag=i;res[0]=0; //注意清空res
                DFS(1);
            }
        }
        else{
            del=-1;tag=1; //不删除任何点
            DFS(1);
        }
        for(int i=1;i<=n;i++)
            printf("%d%c",ans[i],i==n?'
    ':' ');
        return 0;
    }
    

    (mathcal {THE END})

    (mathcal {Thanks For Reading!})

    有什么没看懂的可以随时在 (lucky\_glass@foxmail.com) 邮箱问我 (。・∀・)ノ


    1. 这里的 (O(log_n)) 的复杂度来源于对邻接表的排序操作 ↩︎

  • 相关阅读:
    测试工作效率低思考和改进
    Linux环境变量配置方法
    Linux上error while loading shared libraries问题解决方法
    PyCharm工具配置和快捷键使用
    Linux chattr和lsattr命令使用方法
    PuTTY工具配置和使用方法
    Python+AutoIt实现界面工具开发
    我对测试工作的一些认识
    Windows终端工具_MobaXterm
    Cygwin工具安装和使用指导书
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/10011089.html
Copyright © 2020-2023  润新知