[luogu5022][NOIP2018] 旅行

传送门

这个思路在考场上就想出来了,但是没有写出来很可惜。

对于一棵树来说,求其最小字典序的dfs序非常简单,每次从小到大遍历出边即可。对于边我们考虑事先进行排序,然后再插入到邻接表里。时间复杂度为\(O(N\log N)\)

对于一个图,并且\(N=M\),就可以保证有且只有一个环。那么会出现一种神奇的情况,如果按照树的做法进行dfs:

你的dfs序是132546,但是答案是132456,也就是在2-5这里,直接返回了,从环的另一短继续遍历。

这个过程可以理解为:把2-5这条边删了,然后跑树的遍历

所以我们考虑枚举删边,然后进行dfs

但是会超时啊……所以需要剪枝:当前的dfs序和之前的比已经不会更优了,就直接退出

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 5005

struct edge {
    int u,v;
}E[MAXN],A[MAXN<<1];

struct table {
    int v,next;
}G[MAXN<<1];

int head[MAXN];
bool vis[MAXN];
int ans[MAXN],temp[MAXN];

int N,M,tot = 0;

inline int read() {
    int num = 0; char ch = getchar();
    while(ch<'0'||ch>'9') ch = getchar();
    while(ch>='0'&&ch<='9') num = num*10+ch-48,ch = getchar();
    return num;
}

inline void add(int u,int v) {
    G[++tot].v = v; G[tot].next = head[u]; head[u] = tot;
}

int step = 1;
int flag = 0;

void dfs(int u,int opt,int fa) {
    
    if(ans[step]==0) ans[step] = u;
    else if(ans[step]>u) ans[step] = u,flag = 1;
    else if(flag==1) ans[step] = u;
    else if(ans[step]==u);
    else if(ans[step]<u) {
        flag = -1;
        return;
    }
    step++;

    vis[u] = 1;

    for(int i=head[u];i;i=G[i].next) {
        int v = G[i].v; 
        if(v==fa||vis[v]) continue;
        if(E[opt].u==u&&E[opt].v==v) continue;
        if(E[opt].v==u&&E[opt].u==v) continue;
        dfs(v,opt,u);
        if(flag==-1) return;
    }
}

inline bool cmp(edge a,edge b) {
    return a.v>b.v;
}

int work(int u,int opt,int fa) {
    int size = 1;
    vis[u] = 1;
    for(int i=head[u];i;i=G[i].next) {
        int v = G[i].v; 
        if(v==fa||vis[v]) continue;
        if(E[opt].u==u&&E[opt].v==v) continue;
        if(E[opt].v==u&&E[opt].u==v) continue;
        size += work(v,opt,u);
    }
    return size;
}

int main() {

    N = read(); M = read();
    for(int i=1;i<=M;++i) {
        E[i].u = read(); E[i].v = read();
        A[(i<<1)-1] = E[i];
        A[i<<1] = (edge) {E[i].v,E[i].u};
    }

    std::sort(A+1,A+1+(M<<1),cmp);
    for(int i=1;i<=M<<1;++i) {
        add(A[i].u,A[i].v);
    }

    std::memset(vis,0,sizeof(vis));
    std::memset(ans,0,sizeof(ans));
    step = 1;

    flag = 1;
    if(M==N-1) dfs(1,0,1);
    else {
        for(int i=1;i<=M;++i) {
            std::memset(vis,0,sizeof(vis));
            if(work(1,i,1)!=N) continue;
            std::memset(vis,0,sizeof(vis));
            step = 1; flag = 0;
            dfs(1,i,1);
        }
    }
    for(int i=1;i<N;++i) printf("%d ",ans[i]);
    printf("%d",ans[N]);
    return 0;
}

这里再提供一种做法,虽然没写出来可能有错……

因为环只有一个,如果我们能找到环,是否只需要求出删哪条边最优就好了,那么怎么求这个最优边呢?

环上的起点一定有两条出边,那么我们先选较优的一直走下去,如果下一个点不如起点的另一个点优,那么此时就应该回溯,所以这条边就是我们要的边。

posted @ 2018-11-28 19:17  Neworld1111  阅读(209)  评论(0编辑  收藏  举报