Long Way to be Non-decreasing 题解

\(b[i][j]\) 为用 \(b[i]\) 转移 \(j\) 次。

容易想到这题是二分答案题,一个贪心的思想,对于每一个 \(a_i\) ,在 \(b[a[i]][0]\sim b[a[i]][mid]\) 这些点中大于等于 \(a_{i-1}\) 的最小的值即可,时间复杂度应该是 \(O(n^2\log n)\)

然而,这一题可以转换成图论题,对 \(i\rightarrow b_i\) 这样的图是一个基环森林,然后我们需要找到 \(i \rightarrow b[i][mid]\) 的这一段区间的大于等于 \(a_{i-1}\) 的最小的值即可,这个可以使用数据结构维护,这题就好了。

当然这还是太难写了,而且这个是一个 \(O(n\log^2n)\) 的算法,可能会被卡掉,还是容易被数据结构学傻了。

那这题有一个非常的有趣的性质——单调性。可以将 check 的时间复杂度优化到 \(O(n)\) 。具体这么做呢?我们没必要直接找到 \(a_{i-1}\) ,而是枚举的找到 \(a_{i-1}\) ,看下这个点是否能达到,就是求一下基环树两点的距离是否小于 \(mid\) ,这个结论非常的显然,但是在考场上不太容易想到。

对于基环树的两点的距离,这是一个内向基环树,对于 \(dist(x,y)\) 我们需要分类讨论:

  • 这两个点不在一个基环树上,距离为 \(\inf\)
  • \(x,y\) 不在一个环上的一点的祖先里,且不都在环上,\(\inf\)
  • \(x,y\) 在环上直接求环上距离。
  • \(x,y\) 在同一个子树内,但 \(y\) 不是 \(x\) 的祖先,\(\inf\)
  • \(x,y\) 在同一个子树内,且 $ y$ 是 \(x\) 的祖先,是树上两点的距离。
  • \(x\) 在某个子树内,而 \(y\) 在基环树内,求 \(x\) 的根到 \(y\) 的距离加上 \(x\) 的深度。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5, inf = 1e9;
int n, m, T;
int a[N], b[N], p[N], t;
int flag[N];
int bel[N], idx[N], cnt, siz[N], rt[N], dep[N], L[N], R[N];
bool vis[N];
//记录在环上的点
vector<int> e[N], g[N];
int dist(int x, int y) {
    if (bel[rt[x]] != bel[rt[y]])
        return inf;
    if (rt[x] != rt[y]) {
        if (flag[y])
            return inf;
        return dep[x] + (idx[y] - idx[rt[x]] + siz[bel[y]]) % siz[bel[y]];
    }  //不在同一个环点上
    else {
        if (L[y] <= L[x] && R[x] <= R[y])
            return dep[x] - dep[y];
        else
            return inf;
    }
}  // x->y 的距离
bool check(int mid) {
    for (int i = 1, now = 1; i <= n; i++) {
        while (now <= m && dist(a[i], now) > mid) now++;
        if (now > m)
            return false;
    }
    return true;
}
void redfs(int u, int root) {
    L[u] = ++cnt;
    rt[u] = root;
    for (int v : g[u])
        if (flag[v])
            dep[v] = dep[u] + 1, redfs(v, root);
    R[u] = cnt;
}
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) scanf("%d", a + i);
        for (int i = 1; i <= m; i++)
            e[i].clear(), g[i].clear(), p[i] = vis[i] = flag[i] = rt[i] = dep[i] = siz[i] = 0;
        cnt = 0, t = 0;
        for (int i = 1; i <= m; i++)
            scanf("%d", b + i), e[i].push_back(b[i]), p[b[i]]++, g[b[i]].push_back(i);
        queue<int> q;
        for (int i = 1; i <= m; i++)
            if (!p[i])
                q.push(i), flag[i] = 1;
        while (!q.empty()) {
            int x = q.front();
            q.pop();
            for (int i : e[x])
                if (!--p[i])
                    q.push(i), flag[i] = 1;
        }
        for (int i = 1; i <= m; i++) {
            if (!flag[i]) {
                int now = i;
                if (!vis[now])
                    ++t;
                while (!vis[now]) {
                    vis[now] = 1;
                    bel[now] = t;
                    idx[now] = ++cnt;
                    siz[t]++;
                    now = b[now];
                }
            }
        }
        cnt = 0;
        for (int i = 1; i <= m; i++)
            if (!flag[i])
                redfs(i, i);
        int l = 0, r = m, ans = -1;
        // cout<<dist(6,4)<<endl;
        // cout<<check(0)<<endl;
        // return 0;
        while (l <= r) {
            int mid = l + r >> 1;
            if (check(mid)) {
                ans = mid;
                r = mid - 1;
            } else
                l = mid + 1;
        }
        cout << ans << endl;
    }
    return 0;
}
posted @ 2025-05-14 16:20  hnczy  阅读(21)  评论(0)    收藏  举报