1 条题解

  • 0
    @ 2026-5-14 21:47:12

    1967D - 漫长的非递减之路


    一、题意(极简回顾)

    • 每次操作:选任意位置 xx,令 ax=b[ax]a_x = b[a_x]
    • 目标:用最少操作次数让数组变成 非递减 a1a2ana_1\le a_2\le\dots\le a_n
    • 无解输出 1-1

    二、标程核心思路

    1. 为什么二分答案?

    单独模拟每一步操作无法优化,因此:

    • 二分操作次数 kk
    • 判断:kk 次操作内能否把数组变成非递减

    2. 判断函数:贪心构造(最优策略)

    从左到右构造数组:

    • aia_i 尽可能
    • 满足 aiai1a_i \ge a_{i-1}
    • 满足 aia_i 是初始值 kk 步内能到达的值

    能构造完 k\Rightarrow k 可行。

    3. 图论模型

    每个数字 xx 连边:xb[x]x \to b[x] \Rightarrow 图是 内向基环森林(环 + 挂在环上的树)

    4. O(1)O(1) 可达性判定(标程核心)

    判断 vv 能否 k\le k 步变成 uu

    1. 树上uuvv 子树内,且深度差 k\le k
    2. 经过环:满足步数公式 + 子树包含

    三、完整标程代码(逐行超详细注释)

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int MAXM = 1e6 + 5;
    const int MAXN = 1e6 + 5;
    
    int n, m;
    int a[MAXN], b[MAXM];
    
    // 基环树存储
    vector<int> G[MAXM];
    // 访问标记 / 深度 / dfs序 / 子树大小
    int vis[MAXM], dep[MAXM], dfn[MAXM], sz[MAXM];
    // 根节点 / 环上割边 s->t
    int root[MAXM], cir_s[MAXM], cir_t[MAXM];
    int dfn_idx;
    
    // 环相关
    int in_cycle[MAXM], cycle_id[MAXM];
    vector<int> cycle[MAXM];
    int tot_cycle;
    
    // ---------------------- 1. 找基环树中的环 ----------------------
    int dfs_cycle(int u) {
        vis[u] = 1;
        for (int v : G[u]) {
            if (vis[v] == 1) { // 找到环
                tot_cycle++;
                int cur = u;
                while (cur != v) {
                    in_cycle[cur] = 1;
                    cycle[tot_cycle].push_back(cur);
                    cur = b[cur];
                }
                in_cycle[v] = 1;
                cycle[tot_cycle].push_back(v);
                return v;
            }
            if (!vis[v]) {
                int res = dfs_cycle(v);
                if (res > 0) {
                    if (in_cycle[u]) return -1;
                    in_cycle[u] = 1;
                    cycle[tot_cycle].push_back(u);
                    return (res == u) ? -1 : res;
                }
            }
        }
        vis[u] = 2;
        return -1;
    }
    
    // ---------------------- 2. 处理树结构,预处理 dep, dfn, sz ----------------------
    void dfs_tree(int u, int rt, int s, int t) {
        dfn[u] = ++dfn_idx;
        sz[u] = 1;
        root[u] = rt;
        cir_s[u] = s;
        cir_t[u] = t;
        for (int v : G[u]) {
            if (!in_cycle[v]) { // 只处理树边
                dep[v] = dep[u] + 1;
                dfs_tree(v, rt, s, t);
                sz[u] += sz[v];
            }
        }
    }
    
    // ---------------------- 3. 初始化整张基环树 ----------------------
    void build() {
        // 清空数据
        for (int i = 1; i <= m; i++) {
            G[i].clear();
            vis[i] = in_cycle[i] = cycle_id[i] = 0;
            dep[i] = dfn[i] = sz[i] = 0;
        }
        for (int i = 1; i <= tot_cycle; i++) cycle[i].clear();
        dfn_idx = tot_cycle = 0;
    
        // 建图 x -> b[x]
        for (int i = 1; i <= m; i++) G[b[i]].push_back(i);
    
        // 找所有环
        for (int i = 1; i <= m; i++)
            if (!vis[i]) dfs_cycle(i);
    
        // 处理每个环上的树
        for (int id = 1; id <= tot_cycle; id++) {
            auto& vec = cycle[id];
            int len = vec.size();
            for (int i = 0; i < len; i++) {
                int s = vec[i], t = vec[(i+1)%len];
                dep[s] = 0;
                dfs_tree(s, s, s, t);
            }
        }
    }
    
    // ---------------------- 4. O(1) 判断 v 能否 k 步到达 u ----------------------
    bool reachable(int u, int v, int k) {
        if (u == v) return true;
        if (root[u] != root[v]) return false;
    
        // 情况1:在同一棵树上,v是祖先,u是后代
        if (dfn[v] <= dfn[u] && dfn[u] <= dfn[v] + sz[v] - 1)
            return (dep[u] - dep[v]) <= k;
    
        // 情况2:需要经过环
        int s = cir_s[v], t = cir_t[v];
        // v 能到 s,t 能到 u
        if (!(dfn[v] <= dfn[s] && dfn[s] <= dfn[v] + sz[v] - 1)) return false;
        if (!(dfn[t] <= dfn[u] && dfn[u] <= dfn[t] + sz[t] - 1)) return false;
    
        // 总步数:v->s + 1(环边) + t->u
        int step = (dep[s] - dep[v]) + (dep[u] - dep[t]) + 1;
        return step <= k;
    }
    
    // ---------------------- 5. 二分check:k次操作能否构造合法数组 ----------------------
    bool check(int k) {
        int last = 0;
        for (int i = 1; i <= n; i++) {
            int st = a[i];
            int best = -1;
            // 贪心选最小的 >= last 且可达的值
            for (int w = max(last, 1); w <= m; w++) {
                if (reachable(w, st, k)) {
                    best = w;
                    break;
                }
            }
            if (best == -1) return false;
            last = best;
        }
        return true;
    }
    
    // ---------------------- 主函数 ----------------------
    void solve() {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) cin >> a[i];
        for (int i = 1; i <= m; i++) cin >> b[i];
    
        build();
    
        // 二分答案:0~30 足够(操作次数极少)
        int l = 0, r = 30, ans = -1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (check(mid)) {
                ans = mid;
                r = mid - 1;
            } else l = mid + 1;
        }
        cout << ans << '\n';
    }
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        int t; cin >> t; while (t--) solve();
        return 0;
    }
    

    四、代码结构完全对应标程

    代码模块 标程对应内容
    dfs_cycle 找基环树的环
    dfs_tree 求深度、dfn、子树大小
    reachable O(1)O(1) 判断 kk 步可达
    check 贪心构造非递减数组
    二分 求最小操作次数

    五、复杂度分析(100% 符合题目限制)

    • 建图、找环、DFS:O(m)O(m)
    • 二分:O(log30)=O(1)O(\log 30) = O(1)
    • 每次 check:O(n)O(n)
    • 总复杂度:O(n+m)O(\sum n + \sum m) \Rightarrow 轻松通过 10610^6 数据

    • 1

    信息

    ID
    7068
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者