CrCPC 2024 做题记录

CrCPC 2024 做题记录

D. 排序(DrevniDiskovi)

双向 bfs

数据范围比较小,猜测是搜索。状态空间的大小 \(\le 10! = 3628800\),所以完全可以把整个状态空间遍历一遍。

考虑直接 bfs,对于一个排列,用 \(O(n^{3})\) 的时间枚举四段的三个分界点,再用 \(O(n)\) 的时间拼出新的排列,总时间复杂度 \(O(n! \cdot n^{4})\),不可通过。(如果用 map 记录是否访问过某状态,还要多一个 \(\log n!\),但可以用字符串存排列,然后用 unordered_map 代替 map。)

如果把排列看作状态空间中的点,转移关系看作边,到这一步我们有两种优化的方向:一种是减少边数,即避免无效转移;另一种是减少点数,即避免访问无效的状态。vp 时我们一直在尝试前者,想办法减少一个状态能转移出的后继状态数量,但这似乎是不可行的。但这题的关键点在于终态是唯一确定的,这启示我们使用双向 bfs(这也是官方题解的做法),这本质上减少了访问的状态数。修改后确实可以通过本题。AC 记录

(但是我现在仍然不会严谨地分析这种做法的时间复杂度。std 似乎认为答案不会超过 \(6\),因为它双向搜索的边界都是 \(3\)。测试发现,对于答案为 \(6\) 的输入,入队的状态数大概为 \(70\) 万,远小于上界。)

Code
#include<bits/stdc++.h>

using namespace std;

int n;
string s0, sn;
unordered_map<string, int> f, g;

vector<string> getNext(string s, array<int, 4> p) {
    vector<string> res;
    for(int a = 0; a <= n; a++) {
        for(int b = a; b <= n; b++) {
            for(int c = b; c <= n; c++) {
                array<string, 4> sub;
                sub[0] = s.substr(0, a); // [0, a)
                sub[1] = s.substr(a, b - a); // [a, b)
                sub[2] = s.substr(b, c - b); // [b, c)
                sub[3] = s.substr(c, n - c); // [c, n)
                string nxt = "";
                for(int i = 0; i < 4; i++) {
                    nxt += sub[p[i]];
                }
                res.push_back(nxt);
            }
        }
    }
    return res;
}

vector<string> getNextForwards(string s) {
    return getNext(s, array<int, 4>{2, 0, 3, 1});
}

vector<string> getNextBackwards(string s) {
    return getNext(s, array<int, 4>{1, 3, 0, 2});
}

void bfs() {
    array<queue<string>, 2> que;
    que[0].push(s0), f[s0] = 0;
    que[1].push(sn), g[sn] = 0;
    while(!que[0].empty() || !que[1].empty()) {
        int d;
        if(que[1].empty() || (!que[0].empty() && f[que[0].front()] < g[que[1].front()])) d = 0;
        else d = 1;

        if(d == 0) {
            auto now = que[0].front();
            que[0].pop();
            if(g.count(now)) {
                cout << f[now] + g[now] << '\n'; return;
            }
            vector<string> nxt(getNextForwards(now));
            for(auto tmp: nxt) {
                if(!f.count(tmp)) {
                    f[tmp] = f[now] + 1;
                    que[0].push(tmp);
                }
            }
        } else {
            auto now = que[1].front();
            que[1].pop();
            if(f.count(now)) {
                cout << f[now] + g[now] << '\n'; return;
            }
            vector<string> nxt(getNextBackwards(now));
            for(auto tmp: nxt) {
                if(!g.count(tmp)) {
                    g[tmp] = g[now] + 1;
                    que[1].push(tmp);
                }
            }
        }
    }
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    f.max_load_factor(0.3f);
    g.max_load_factor(0.3f);

    cin >> n;
    s0.resize(n), sn.resize(n);
    for(int i = 0, x; i < n; i++) {
        cin >> x;
        s0[i] = (char)(x - 1 + '0');
        sn[i] = (char)(i + '0');
    }

    bfs();
    
    return 0;
}

H. 信步山中(Hoditi Hribima)

图论建模,最短路

同时考虑两个图是不方便的,不妨合成到一个图考虑。注意到每次移动能到达的点和移动次数的奇偶性有关,所以把每个点拆成两个,分别代表第奇数次移动和第偶数次移动。下面用 \((u, 1 / 0)\) 表示在点 \(u\),且下一次移动是第奇数/偶数次。

先用 Dijkstra 求出两个图中所有节点到 \(t\) 的距离,然后就可以按题目要求建出新图 \(G\),它有 \(2n\) 个点和不超过 \(m_1 + m_2\) 条边。那么我们要求的就是 \((s, 1)\)\((t, 0)\) \((t, 1)\) 的最长路。这里有两种情况使得答案为无穷大:

  1. 新图中 \((s, 1)\)\((t, 0)\)\((t, 1)\) 都不连通。

  2. \((s, 1)\) 出发可以到达一个不经过 \((t, 0)\)\((t, 1)\) 的正环。(根据题目描述,到达 \((t, 0)\)\((t, 1)\) 之后就不能再移动了。)

第一种情况容易判断。对于第二种情况,我们可以直接删除 \(G\)\((t, 0)\)\((t, 1)\) 的所有出边,然后再判断是否存在正环,这样就不用考虑环经过了 \((t, 0)\)\((t, 1)\) 的情况。由于边权都为正,所以只需判断是否存在 \((s, 0)\) 能到达的环,可以用 SPFA 或者拓扑排序解决。前者的时间复杂度为 \(O(nm)\),但本题似乎没有特意去卡(其实我也不知道能不能卡,因为 \(G\) 是自己建的图),因此可以通过;后者的时间复杂度为 \(O(n + m)\)

我们可以在判断这些特殊情况的同时求出最长路。总时间复杂度为 \(O(n + m \log m)\)(如果使用拓扑排序)。

AC 记录:SPFA | 拓扑排序

SPFA
#include<bits/stdc++.h>

using namespace std;

constexpr int INF = 0x3f3f3f3f;
int n, s, t;
struct Edge {
    int v, w;
};
vector<vector<Edge>> G;
array<vector<vector<Edge>>, 2> Gs;
array<int, 2> ms;
array<vector<int>, 2> diss;
vector<int> ans;

struct Node {
    int u, d;
    bool operator < (const Node &rhs) const {
        return d > rhs.d;
    }
};

void Dijkstra(vector<vector<Edge>> &T, vector<int> &dis) {
    dis.resize(n, INF);
    vector<int> vis(n);
    priority_queue<Node> heap;
    heap.push({t, 0}), dis[t] = 0;
    while(!heap.empty()) {
        auto [u, d] = heap.top();
        heap.pop();
        if(vis[u]) {
            continue;
        }
        vis[u] = true;
        for(auto [v, w]: T[u]) {
            if(dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                heap.push({v, dis[v]});
            }
        }
    }
}

bool spfa() {
    vector<int> vis(2 * n);
    vector<int> cnt(2 * n);
    ans.resize(2 * n, INF);
    fill(ans.begin(), ans.end(), -INF);
    queue<int> que;
    que.push(s + n), ans[s + n] = 0;
    while(!que.empty()) {
        int u = que.front();
        que.pop();
        vis[u] = false;
        for(auto [v, w]: G[u]) {
            if(ans[u] + w > ans[v]) {
                ans[v] = ans[u] + w;
                if(++cnt[v] == 2 * n) {
                    return true;
                }
                que.push(v);
            }
        }
    }
    return false;
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    cin >> n >> s >> t;
    --s, --t;

    for(int i: {1, 0}) {
        cin >> ms[i];
        Gs[i].resize(n);
        for(int j = 0, u, v, w; j < ms[i]; j++) {
            cin >> u >> v >> w;
            --u, --v;
            Gs[i][u].push_back({v, w}), Gs[i][v].push_back({u, w});
        }
        Dijkstra(Gs[i], diss[i]);
    }

    G.resize(2 * n);
    for(int i = 0; i < 2 * n; i++) {
        int o = i / n, u = i % n; // o = 0: even; o = 1: odd
        for(auto [v, w]: Gs[o][u]) {
            if(diss[o][u] > diss[o][v]) {
                G[i].push_back({v + (o ^ 1) * n, w});
            }
        }
    }

    G[t].clear(), G[n + t].clear();
    if(spfa() || max(ans[t], ans[t + n]) == -INF) {
        cout << -1 << '\n';
    } else {
        cout << max(ans[t], ans[t + n]) << '\n';
    }

    return 0;
}
拓扑排序
#include<bits/stdc++.h>

using namespace std;

constexpr int INF = 0x3f3f3f3f;
int n, s, t;
struct Edge {
    int v, w;
};
vector<vector<Edge>> G;
array<vector<vector<Edge>>, 2> Gs;
array<int, 2> ms;
array<vector<int>, 2> diss;
vector<int> ans, deg, reach;

struct Node {
    int u, d;
    bool operator < (const Node &rhs) const {
        return d > rhs.d;
    }
};

void Dijkstra(vector<vector<Edge>> &T, vector<int> &dis) {
    dis.resize(n, INF);
    vector<int> vis(n);
    priority_queue<Node> heap;
    heap.push({t, 0}), dis[t] = 0;
    while(!heap.empty()) {
        auto [u, d] = heap.top();
        heap.pop();
        if(vis[u]) {
            continue;
        }
        vis[u] = true;
        for(auto [v, w]: T[u]) {
            if(dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                heap.push({v, dis[v]});
            }
        }
    }
}

void dfs(int u) {
    reach[u] = 1;
    for(auto [v, w]: G[u]) {
        deg[v]++;
        if(!reach[v]) {
            dfs(v);
        }
    }
}

bool toposort() {
    ans.resize(2 * n);
    queue<int> que;
    for(int i = 0; i < 2 * n; i++) {
        if(!deg[i] && reach[i]) {
            que.push(i);
        }
    }

    while(!que.empty()) {
        int u = que.front();
        que.pop();
        for(auto [v, w]: G[u]) {
            ans[v] = max(ans[v], ans[u] + w);
            if(--deg[v] == 0) {
                que.push(v);
            }
        }
    }

    for(int i = 0; i < 2 * n; i++) {
        if(deg[i] && reach[i]) {
            return true;
        }
    }
    return false;
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    cin >> n >> s >> t;
    --s, --t;

    for(int i: {1, 0}) {
        cin >> ms[i];
        Gs[i].resize(n);
        for(int j = 0, u, v, w; j < ms[i]; j++) {
            cin >> u >> v >> w;
            --u, --v;
            Gs[i][u].push_back({v, w}), Gs[i][v].push_back({u, w});
        }
        Dijkstra(Gs[i], diss[i]);
    }

    G.resize(2 * n), deg.resize(2 * n);
    for(int i = 0; i < 2 * n; i++) {
        int o = i / n, u = i % n; // o = 0: even; o = 1: odd
        for(auto [v, w]: Gs[o][u]) {
            if(diss[o][u] > diss[o][v]) {
                G[i].push_back({v + (o ^ 1) * n, w});
            }
        }
    }

    reach.resize(2 * n);
    dfs(s + n);
    if(!reach[t] && !reach[t + n]) {
        cout << "-1\n"; return 0;
    }

    G[t].clear(), G[n + t].clear();
    if(toposort()) {
        cout << -1 << '\n';
    } else {
        cout << max(ans[t], ans[t + n]) << '\n';
    }

    return 0;
}
posted @ 2025-03-19 08:32  DengStar  阅读(103)  评论(0)    收藏  举报