洛谷P5022 旅行(NOIP提高组2018 D2T1)题解 贪心/去环

题目链接:https://www.luogu.com.cn/problem/P5022

解题思路:

这里最重要的是数据范围里面的 \(m=n-1\) 或者 \(m=n\)

\(m=n-1\) 的时候是一棵树,我们按照从当前节点找编号最小的子节点的策略进行深搜就能够解决这个问题。

\(m=n\) 的时候只存在一个环,我们只需要找到环上面的每一条边,然后枚举每一条边,在假设这条边被去掉的情况下(去掉一条环上面的边这个图就又变成了树)进行给予上述贪心思想的搜索。

所以总的时间复杂度是 \(O(n^2)\) (因为这里 \(n\)\(m\) 最多相差 \(1\),所以这么说)。

实现起来要注意一些袭击,比如邻接表最好一遍排序,接下来就用就可以了。

实现代码如下(略显繁琐):

#include 
using namespace std;
const int maxn = 5050;
struct Edge {
    int u, v, nxt;
    Edge() {};
    Edge(int _u, int _v, int _nxt) { u = _u; v = _v; nxt = _nxt; }
} edge[maxn<<1];
int n, m, ecnt, head[maxn];
void init() {
    ecnt = 0;
    memset(head, -1, sizeof(int)*(n+1));
}
void addedge(int u, int v) {
    edge[ecnt] = Edge(u, v, head[u]); head[u] = ecnt ++;
    edge[ecnt] = Edge(v, u, head[v]); head[v] = ecnt ++;
}
bool flag[maxn<<1], vise[maxn<<1];
int fe[maxn<<1], dep[maxn];
void dfs1(int u, int d) {   // 用来构造一棵树,从而找到没有使用的那条边
    dep[u] = d;
    for (int i = head[u]; i != -1; i = edge[i].nxt) {
        int v = edge[i].v;
        if (!dep[v]) {
            dep[v] = dep[u] + 1;
            vise[i] = vise[i^1] = true;
            fe[v] = i^1;
            dfs1(v, d+1);
        }
    }
}
void after_dfs1() { // 标记所有在环上的边
    int u, v;
    for (int i = 0; i < ecnt; i += 2) {
        if (!vise[i]) {
            flag[i] = flag[i^1] = true;
            u = edge[i].u;
            v = edge[i].v;
            break;
        }
    }
    while (u != v) {
        if (dep[u] < dep[v]) swap(u, v);
        int i = fe[u];
        flag[i] = flag[i^1] = true;
        u = edge[i].v;
    }
}
vector g[maxn];
int gsz[maxn];
int ans[maxn], tmp[maxn], tmpcnt;
void before_dfs2() {
    for (int u = 1; u <= n; u ++) {
        for (int i = head[u]; i != -1; i = edge[i].nxt) {
            int v = edge[i].v;
            g[u].push_back(v);
        }
        sort(g[u].begin(), g[u].end());
        gsz[u] = g[u].size();
    }
}
void dfs2(int u, int p, int ii) {
    // printf("dfs2 : u = %d , p = %d , ii = %d\n", u, p, ii);
    tmp[tmpcnt++] = u;
    int sz = gsz[u];
    // printf("\tg[%d].size() == %d\n", u, sz);
    for (int i = 0; i < sz; i ++) {
        int v = g[u][i];
        if (v != p && !(u == edge[ii].u && v == edge[ii].v) && !(u == edge[ii].v && v == edge[ii].u))
            dfs2(v, u, ii);
    }
}
int main() {
    scanf("%d%d", &n, &m);
    init();
    for (int i = 0; i < m; i ++) {
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
    }
    before_dfs2();
    if (m == n-1) { // 本身就是一棵树
        dfs2(1, -1, -1);
        for (int i = 0; i < n; i ++) printf("%d ", tmp[i]);
    }
    else {  // 存在一个环
        dfs1(1, 1);
        after_dfs1();
        bool first = true;
        for (int i = 0; i < ecnt; i += 2) {
            if (flag[i]) {
                tmpcnt = 0;
                dfs2(1, -1, i);
                bool flag = false;
                if (first) {
                    flag = true;
                }
                else {
                    for (int i = 0; i < n; i ++) {
                        if (ans[i] != tmp[i]) {
                            if (ans[i] > tmp[i]) flag = true;
                            break;
                        }
                    }
                }
                if (flag) {
                    if (first) {
                        first = false;
                    }
                    for (int i = 0; i < n; i ++) ans[i] = tmp[i];
                }
            }
        }
        for (int i = 0; i < n; i ++) printf("%d ", ans[i]);
    }
    return 0;
}

你可能感兴趣的:(洛谷P5022 旅行(NOIP提高组2018 D2T1)题解 贪心/去环)