1 条题解

  • 0
    @ 2026-4-17 21:06:45

    F. Field Should Not Be Empty 题解

    题意概述

    给定一个排列 pp

    如果下标 xx 满足:

    • 对所有 y<xy < x,都有 py<pxp_y < p_x
    • 对所有 y>xy > x,都有 py>pxp_y > p_x

    那么称 xx 是一个好下标。

    f(p)f(p) 为好下标个数。现在必须恰好交换一次两个不同位置,求交换后 f(p)f(p) 的最大值。

    第一步:把“好下标”改写成区间覆盖问题

    对每个位置 ii,考虑区间

    [min(i,pi),max(i,pi)].[\,\min(i,p_i),\max(i,p_i)\,].

    把每个这样的区间都加一。

    结论

    下标 xx 是好下标,当且仅当位置 xx 的覆盖次数恰好为 11

    为什么成立

    位置 xx 一定会被自己的区间覆盖一次,因为 x[min(x,px),max(x,px)]x \in [\min(x,p_x),\max(x,p_x)]

    如果还有别的某个位置 ixi \ne x 的区间也覆盖了 xx,那么就说明:

    • 要么 i<xpii < x \le p_i,即左边有一个值跑到了 xx 的右边;
    • 要么 pix<ip_i \le x < i,即右边有一个值跑到了 xx 的左边。

    这两种情况分别等价于:

    • 左边存在某个位置 i<xi < x 使得 pi>xp_i > x
    • 右边存在某个位置 i>xi > x 使得 pi<xp_i < x

    而对于排列来说,

    $$\forall y < x,\ p_y < p_x \quad \text{且} \quad \forall y > x,\ p_y > p_x $$

    恰好等价于“没有任何别的区间跨过 xx”。因此覆盖次数为 11 当且仅当 xx 是好下标。

    于是问题就变成了:

    • 一次交换只会改变两个位置对应的区间;
    • 我们需要动态维护整张覆盖数组中值为 11 的位置个数。

    第二步:为什么只需要枚举 2n2n 次交换

    这题最关键的观察是:绝大多数交换都不可能让答案变大。

    性质 11:好下标一定是固定点

    xx 是好下标,那么左边一共有 x1x-1 个数,并且它们都小于 pxp_x;右边一共有 nxn-x 个数,并且它们都大于 pxp_x

    由于 pp 是排列,恰好有 px1p_x-1 个数比 pxp_x 小。因此必须有

    px1=x1,p_x - 1 = x - 1,

    也就是

    px=x.p_x = x.

    所以任何好下标都必须是固定点。

    第一类有意义的交换:(i, pos[i])

    如果一次交换想新建一个好下标 ii,那么交换后必须有 pi=ip_i=i

    而在排列中,值 ii 只出现一次,所以唯一可能的做法就是把位置 ii 和值 ii 当前所在的位置 pos[i] 交换。

    因此所有“新建固定点”的交换只有这 nn 个:

    (i,pos[i]).(i,\operatorname{pos}[i]).

    第二类有意义的交换:修复一个不合格的固定点

    如果某个位置 ii 在交换中没有被动到,但它想在交换后变成好下标,那么它本来就必须满足 pi=ip_i=i

    此时它不好的原因只能是:

    • 左边存在值大于 ii 的元素;
    • 右边存在值小于 ii 的元素。

    设:

    $$L_i=\{\, y \lt i \mid p_y \gt i \,\},\qquad R_i=\{\, y \gt i \mid p_y \lt i \,\}. $$

    因为 pp 是排列,所以一定有

    Li=Ri.|L_i|=|R_i|.

    一次交换最多只能把左边一个坏元素和右边一个坏元素对调,因此只有在

    Li=Ri=1|L_i|=|R_i|=1

    时,位置 ii 才可能被一次交换修好。

    这时左边那个唯一的坏元素就是左侧前缀中的最大值所在位置,记为 kk;右边那个唯一的坏元素就是右侧后缀中的最小值所在位置,记为 ll。唯一有意义的交换就是:

    (k,l).(k,l).

    所以第二类交换对每个位置最多也只贡献一个候选,总数也是 O(n)O(n)

    小结

    所有可能让答案变大的交换,只会出现在下面两类中:

    1. (i, pos[i])
    2. (k, l),其中 kk 是某个位置左边唯一的“过大元素”,ll 是它右边唯一的“过小元素”

    总候选数不超过 2n2n

    第三步:如何快速算某次交换后的 f(p)f(p)

    既然我们已经把问题转成了区间覆盖,那么自然想到线段树维护整段最小值和最小值出现次数。

    线段树维护什么

    对每个位置 ii,把区间

    [min(i,pi),max(i,pi)][\,\min(i,p_i),\max(i,p_i)\,]

    在线段树上加一。

    线段树每个节点维护:

    • 当前区间的最小覆盖值;
    • 当前区间内最小值出现了多少次。

    再配合懒标记做区间加法。

    为什么根节点就能得到答案

    每个位置至少会被自己的区间覆盖一次,因此整个数组的最小覆盖值不可能小于 11

    于是:

    • 如果整棵线段树的最小值是 11,那么“最小值出现次数”就是好下标个数;
    • 如果最小值大于 11,那么不存在好下标,答案为 00

    这正对应代码里的:

    function<int()> count = [&]()
    {
        if (lesgo.a[1].first == 1)
        {
            return lesgo.a[1].second;
        }
        return 0;
    };
    

    为什么交换时只需要改两个区间

    若交换位置 i,ji,j,只有 pi,pjp_i,p_j 发生变化,其余位置的值都不变。

    因此区间集合里只有这两个区间需要删除再加入:

    1. 先把旧的 i,ji,j 两个区间减掉;
    2. 交换 pi,pjp_i,p_j
    3. 再把新的 i,ji,j 两个区间加回去。

    对应代码:

    function<void(int, int)> mySwap = [&](int i, int j)
    {
        upd(i, -1);
        upd(j, -1);
        swap(a[i], a[j]);
        upd(i, 1);
        upd(j, 1);
    };
    

    所以每次枚举候选交换,只需要 O(logn)O(\log n) 就能得到交换后的 f(p)f(p)

    第四步:第二类交换怎么在线性预处理出来

    我们要对每个位置 ii 判断:

    • 左边是否恰好只有一个元素大于 aia_i
    • 右边是否恰好只有一个元素小于 aia_i

    如果两边都恰好只有一个,那么它们的位置就是第二类交换的候选端点。

    找左边唯一的大元素

    从左到右扫描。

    单调栈

    维护一个按值严格递减的栈。

    对于当前位置 ii

    • 弹掉所有比 aia_i 小的元素;
    • 栈顶如果存在,它就是离 ii 最近、且值大于 aia_i 的位置。

    如果左边恰好只有一个比 aia_i 大的元素,那么它一定就是这个栈顶位置。

    树状数组

    再用一个值域上的树状数组,统计前面出现过哪些值。

    查询区间

    [ai,n][a_i,n]

    中有多少个数。如果这个数量大于 11,说明左边至少有两个值大于 aia_i,那就不可能靠一次交换修好,直接把左端点记为无效。

    代码对应:

    while (!s.empty() && a[s.top()] < a[i])
    {
        s.pop();
    }
    if (!s.empty())
    {
        qui[i].first = s.top();
    }
    if (tree.query(a[i], n) > 1)
    {
        qui[i].first = 0;
    }
    

    找右边唯一的小元素

    完全对称地,从右往左扫描:

    • 用单调栈找最近的小元素;
    • 用树状数组统计右边有多少个值小于 aia_i

    代码对应:

    while (!s.empty() && a[s.top()] > a[i])
    {
        s.pop();
    }
    if (!s.empty())
    {
        qui[i].second = s.top();
    }
    if (tree.query(1, a[i]) > 1)
    {
        qui[i].second = 0;
    }
    

    这样就能在 O(nlogn)O(n \log n) 内把第二类候选全部找出来。

    特判:已经是升序排列

    如果 p=[1,2,,n]p=[1,2,\dots,n],那么原本 f(p)=nf(p)=n,但题目要求必须交换两个不同位置。

    此时交换任意相邻的两个位置,就会恰好破坏这两个位置,所以最优答案是:

    n2.n-2.

    代码里单独做了这个特判。

    整体算法

    对于每组数据:

    1. 先特判是否已经完全升序。
    2. 预处理 pos[i],得到第一类交换 (i, pos[i])
    3. 用单调栈 + 树状数组预处理每个位置的第二类交换候选 (k,l)
    4. 建好维护区间覆盖的线段树。
    5. 枚举所有第一类、第二类候选交换,每次用 mySwap 临时执行,查询当前好下标个数,再交换回来。
    6. 取最大值。

    正确性说明

    性质 11

    对任意位置 xx,覆盖次数等于 11 当且仅当没有别的区间跨过 xx,这等价于:

    $$\forall y<x,\ p_yx,\ p_y>p_x. $$

    所以线段树维护出的“值为 11 的位置个数”恰好就是 f(p)f(p)

    性质 22

    任何好下标都必须满足 px=xp_x=x。因此若一次交换想新建好下标 xx,它必须把值 xx 交换到位置 xx,也就是第一类交换 (x, pos[x])

    性质 33

    如果一次交换不移动位置 ii,却想让 ii 变成好下标,那么它原本必须已经是固定点。此时所有破坏条件的元素只能分成左侧过大元素和右侧过小元素两类,而且两类个数相等。

    一次交换至多处理每侧各一个,因此只有在两侧都恰好只有一个坏元素时可行,而且唯一可行交换就是把这两个坏元素交换,也就是第二类交换。

    性质 44

    除这两类交换外,任何交换都不会新建好下标,也不会修好某个已经是固定点但不合法的位置,因此不可能让答案更优。

    结论

    我们枚举了所有可能使答案变大的交换;对每个候选交换,线段树都能正确计算交换后的 f(p)f(p)。因此算法得到的最大值就是正确答案。

    复杂度分析

    单调栈与树状数组预处理是 O(nlogn)O(n \log n)

    候选交换数量是 O(n)O(n),每次在线段树上做常数次区间加减,复杂度也是 O(logn)O(\log n),所以枚举部分总复杂度仍为:

    O(nlogn).O(n \log n).

    空间复杂度为:

    O(n).O(n).

    参考代码

    #include <bits/stdc++.h>
    using namespace std;
    struct aint
    {
        vector<pair<int, int>> a;
        vector<int> lazy;
        void resize(int n)
        {
            a = vector<pair<int, int>>(4 * n);
            lazy = vector<int>(4 * n);
        }
        void init(int node, int left, int right)
        {
            a[node].second = (right - left + 1);
            if (left != right)
            {
                int mid = (left + right) / 2;
                init(2 * node, left, mid);
                init(2 * node + 1, mid + 1, right);
            }
        }
        void prop(int node, int left, int right)
        {
            a[node].first += lazy[node];
            if (left != right)
            {
                lazy[2 * node] += lazy[node];
                lazy[2 * node + 1] += lazy[node];
            }
            lazy[node] = 0;
        }
        pair<int, int> merge(pair<int, int> a, pair<int, int> b)
        {
            if (a.first == b.first)
            {
                return pair<int, int>{a.first, a.second + b.second};
            }
            return min(a, b);
        }
        void update(int node, int left, int right, int st, int dr, int val)
        {
            prop(node, left, right);
            if (right < st || left > dr)
            {
                return;
            }
            if (st <= left && dr >= right)
            {
                lazy[node] += val;
                prop(node, left, right);
                return;
            }
            int mid = (left + right) / 2;
            update(2 * node, left, mid, st, dr, val);
            update(2 * node + 1, mid + 1, right, st, dr, val);
            a[node] = merge(a[2 * node], a[2 * node + 1]);
        }
    };
    struct bit
    {
        vector<int> a;
        void resize(int n)
        {
            a = vector<int>(n + 1);
        }
        void update(int pos, int val)
        {
            int n = (int)a.size() - 1;
            for (int i = pos; i <= n; i += i & (-i))
            {
                a[i] += val;
            }
        }
        int query(int pos)
        {
            int ans = 0;
            for (int i = pos; i; i -= i & (-i))
            {
                ans += a[i];
            }
            return ans;
        }
        int query(int st, int dr)
        {
            return query(dr) - query(st - 1);
        }
    };
    int32_t main()
    {
        cin.tie(nullptr)->sync_with_stdio(false);
        int q;
        cin >> q;
        while (q--)
        {
            int n;
            cin >> n;
            vector<int> a(n + 1);
            bool sortat = true;
            for (int i = 1; i <= n; ++i)
            {
                cin >> a[i];
                if (a[i] != i)
                {
                    sortat = false;
                }
            }
            if (sortat)
            {
                cout << n - 2 << '\n';
                continue;
            }
            vector<pair<int, int>> qui(n + 1);
            stack<int> s;
            bit tree;
            tree.resize(n);
            for (int i = 1; i <= n; ++i)
            {
                while (!s.empty() && a[s.top()] < a[i])
                {
                    s.pop();
                }
                if (!s.empty())
                {
                    qui[i].first = s.top();
                }
                if (tree.query(a[i], n) > 1)
                {
                    qui[i].first = 0;
                }
                tree.update(a[i], 1);
                s.push(i);
            }
            while (!s.empty())
                s.pop();
            tree.resize(n);
            for (int i = n; i >= 1; --i)
            {
                while (!s.empty() && a[s.top()] > a[i])
                {
                    s.pop();
                }
                if (!s.empty())
                {
                    qui[i].second = s.top();
                }
                if (tree.query(1, a[i]) > 1)
                {
                    qui[i].second = 0;
                }
                tree.update(a[i], 1);
                s.push(i);
            }
            aint lesgo;
            lesgo.resize(n);
            lesgo.init(1, 1, n);
            function<void(int, int)> upd = [&](int ind, int sign)
            {
                lesgo.update(1, 1, n, min(ind, a[ind]), max(ind, a[ind]), sign);
            };
            function<int()> count = [&]()
            {
                if (lesgo.a[1].first == 1)
                {
                    return lesgo.a[1].second;
                }
                return 0;
            };
            function<void(int, int)> mySwap = [&](int i, int j)
            {
                upd(i, -1);
                upd(j, -1);
                swap(a[i], a[j]);
                upd(i, 1);
                upd(j, 1);
            };
            for (int i = 1; i <= n; ++i)
            {
                upd(i, 1);
            }
            int ans = 0;
            for (int i = 1; i <= n; ++i)
            {
                if (qui[i].first && qui[i].second)
                {
                    mySwap(qui[i].first, qui[i].second);
                    ans = max(ans, count());
                    mySwap(qui[i].first, qui[i].second);
                }
            }
            vector<int> pos(n + 1);
            for (int i = 1; i <= n; ++i)
            {
                pos[a[i]] = i;
            }
            for (int i = 1; i <= n; ++i)
            {
                int qui1 = i;
                int qui2 = pos[i];
                mySwap(qui1, qui2);
                ans = max(ans, count());
                mySwap(qui1, qui2);
            }
            cout << ans << '\n';
        }
    }
    • 1

    信息

    ID
    6561
    时间
    1000ms
    内存
    256MiB
    难度
    8
    标签
    递交数
    0
    已通过
    0
    上传者