Codeforces Round 1019 (Div. 2)个人题解(2103D )

Codeforces Round 1019 (Div. 2)个人题解(2103D

题目翻译

数组 \(b_1, b_2, \ldots, b_m\) 中的元素 \(b_i\) ( \(1\le i\le m\) ) 是局部最小值,如果以下条件至少有一个成立:

  • \(2\le i\le m - 1\)\(b_i \lt b_{i - 1}\)\(b_i \lt b_{i + 1}\) ,或
  • \(i = 1\)\(b_1 \lt b_2\) ,或
  • \(i = m\)\(b_m \lt b_{m - 1}\)

同样地,如果以下条件至少有一个成立,那么数组 \(b_1, b_2, \ldots, b_m\) 中的元素 \(b_i\) ( \(1\le i\le m\) )就是局部最大值:

  • \(2\le i\le m - 1\)\(b_i \gt b_{i - 1}\)\(b_i \gt b_{i + 1}\) ,或
  • \(i = 1\)\(b_1 \gt b_2\) ,或
  • \(i = m\)\(b_m \gt b_{m - 1}\)

请注意,只有一个元素的数组不定义局部最小值和最大值。

有一个隐藏的\(\text{排列}^{\text{∗}}\) \(p\) 长度为 \(n\) 。从操作 1 开始,交替对排列 \(p\) 进行以下两次操作,直到 \(p\) 中只剩下一个元素:

  • 操作 1 - 删除 \(p\) 中所有不是局部最小值的元素。
  • 操作 2 - 删除 \(p\) 中所有不是局部最大值的元素。

更具体地说,操作 1 在每次奇数迭代中执行,操作 2 在每次偶数迭代中执行,直到 \(p\) 中只剩下一个元素。

对于每个索引 \(i\) ( \(1\le i\le n\) ),设 \(a_i\) 为元素 \(p_i\) 被删除的迭代次数,如果从未删除,则为 \(-1\)

可以证明, \(p\) 中最多经过 \(\lceil \log_2 n\rceil\) 次迭代(换句话说, \(a_i \le \lceil \log_2 n\rceil\) 次迭代)就只剩下一个元素。

给你一个数组 \(a_1, a_2, \ldots, a_n\) 。你的任务是构造出满足数组 \(a\)\(n\) 元素的任意排列 \(p\)

\(^{\text{∗}}\) 长度为 \(n\) 的排列是由 \(n\) 个不同的整数组成的数组,这些整数从 \(1\)\(n\) 按任意顺序排列。例如, \([2,3,1,5,4]\) 是一个排列,但 \([1,2,2]\) 不是一个排列( \(2\) 在数组中出现了两次), \([1,3,4]\) 也不是一个排列( \(n=3\) ,但数组中有 \(4\) )。

输入

每个测试包含多个测试用例。第一行包含测试用例的数量 \(t\) ( \(1 \le t \le 10^4\) )。测试用例说明如下。

每个测试用例的第一行都包含一个整数 \(n\) ( \(2 \le n \le 2 \cdot 10^5\) )--排列 \(p\) 中的元素个数。

每个测试用例的第二行包含 \(n\) 个整数 \(a_1, a_2, \ldots, a_n\) ( \(1 \le a_i \le \lceil\log_2 n\rceil\)\(a_i = -1\) )--元素 \(p_i\) 被移除的迭代次数。

保证所有测试用例中 \(n\) 的总和不超过 \(2 \cdot 10^5\)

保证至少有一种排列 \(p\) 满足数组 \(a\)

输出

对于每个测试用例,输出 \(n\) 个整数,这些整数代表满足排列数组 \(a\) 的元素。

如果有多个解决方案,可以输出其中任何一个。

样例

输入

7
3
1 1 -1
5
1 -1 1 2 1
8
3 1 2 1 -1 1 1 2
7
1 1 1 -1 1 1 1
5
1 1 1 1 -1
5
-1 1 1 1 1
5
-1 1 2 1 2

输出

3 2 1
4 3 5 1 2
6 7 2 4 3 8 5 1
6 5 2 1 3 4 7
5 4 3 2 1
1 2 3 4 5
4 5 2 3 1

在第一个测试用例中,将对排列 \([3, 2, 1]\) 进行如下操作:

  1. \([3, 2, 1]\) 中唯一的局部最小值是 \(1\) 。因此,移除元素 \(3\)\(2\) 。只剩下一个元素,因此过程结束。

这满足数组 \(a = [1, 1, -1]\) 的要求,因为 \(p_1\)\(p_2\) 都在迭代次数 \(1\) 中被删除,而 \(p_3\) 没有被删除。

在第二个测试案例中,将对排列 \(p = [4, 3, 5, 1, 2]\) 进行如下操作:

  1. \([4, 3, 5, 1, 2]\) 中的局部最小值是 \(3\)\(1\) 。因此,删除元素 \(4\)\(5\)\(2\)
  2. \([3, 1]\) 中唯一的局部最大值是 \(3\) 。因此, \(1\) 元素被删除。只剩下一个元素,因此过程结束。

由于元素 \(p_1 = 4\)\(p_3 = 5\)\(p_5 = 2\) 在迭代 \(1\) 中被删除,元素 \(p_4 = 1\) 在迭代 \(2\) 中被删除,元素 \(p_2 = 3\) 没有被删除,因此满足数组 \(a = [1, -1, 1, 2, 1]\) 的要求。

在第三个测试案例中,将对排列 \([6, 7, 2, 4, 3, 8, 5, 1]\) 进行如下操作:

  1. \([6, 7, 2, 4, 3, 8, 5, 1]\) 中的局部最小值是 \(6\)\(2\)\(3\)\(1\) 。因此,删除元素 \(7\)\(4\)\(8\)\(5\)
  2. \([6, 2, 3, 1]\) 中的局部最大值是 \(6\)\(3\) 。因此,元素 \(2\)\(1\) 被删除。
  3. \([6, 3]\) 中唯一的局部最小值是 \(3\) 。因此, \(6\) 元素被删除。只剩下一个元素,因此过程结束。

在第四个测试用例中,一个满足约束条件的排列是:[ \(6\) , \(5\) , \(2\) , \(1\) , \(3\) , \(4\) , \(7\) }。 \(1\) 是唯一的局部最小值,因此只有它能在第一次迭代后保持不变。请注意,还有其他有效的排列;例如,[ \(6\) , \(4\) , \(3\) , \(1\) , \(2\) , \(5\) , \(7\) ] 也会被认为是正确的。

解题思路

读取题意后可知,在删除数字前,剩下的数字如果次序为奇数,一定会小于相邻的数字;如果次序为偶数,一定会大于相邻的数字。因此,我们可以倒着模拟删除的操作,从最后一次操作开始向对更高层和当前层的关系进行建边,每建完一层的边就将这两层进行合并。

写完之后发现,过不去样例一,因此进行打表,打表发现,由于边界的特殊性,同层之间存在根据次序奇偶性进行改变的大小关系,因此,还需要对同层之间进行建边。

建边关系为小向大,这样,得到的图的拓扑序即为题目索取排列。

解题代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL

bool check(const vi &p, const vi &a)
{
    int n = p.size() - 1;
    // order 存储当前未移除的下标序列,removed_round 记录每个下标被移除的轮次
    vi order(n);
    iota(order.begin(), order.end(), 1);
    vi removed_round(n + 1);
    int round = 1;
    // 每轮交替保留局部极值
    while (order.size() > 1)
    {
        vi nxt;
        bool isOdd = (round & 1);
        // 遍历当前序列,判断是否保留
        for (int idx = 0; idx < (int)order.size(); ++idx)
        {
            int i = order[idx];
            int val = p[i];
            // 求左右邻居值,边界处将自身加减 1,确保边界元素也能被正确判断
            int prev = (idx > 0 ? p[order[idx - 1]] : val + (isOdd ? 1 : -1));
            int next = (idx + 1 < (int)order.size() ? p[order[idx + 1]] : val + (isOdd ? -1 : 1));
            // 奇数轮保留局部最小值,偶数轮保留局部最大值
            bool keep = isOdd ? (val < prev && val < next)
                              : (val > prev && val > next);
            if (keep)
                nxt.push_back(i);
            else
                removed_round[i] = round;
        }
        order.swap(nxt);
        ++round;
    }
    // 最后一轮剩余的元素标记为 -1
    removed_round[order[0]] = -1;
    // 与目标轮次数组 a 对比
    for (int i = 1; i <= n; ++i)
    {
        if (removed_round[i] != a[i])
            return false;
    }
    return true;
}

void dabiao(const vi &a)
{
    int n = a.size() - 1;
    // 初始化排列 p 为 [1, 2, ..., n]
    vi p(n + 1);
    iota(p.begin() + 1, p.end(), 1);
    // 依次生成下一个排列,直到满足条件
    do
    {
        if (check(p, a))
        {
            // 输出结果
            for (int i = 1; i <= n; ++i)
                cout << p[i] << " \n"[i == n];
            return;
        }
    } while (next_permutation(p.begin() + 1, p.end()));
}

void solve()
{
    int n;
    cin >> n;
    vi a(n + 1);
    int end = 0;
    int mx = -1;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        if (a[i] == -1)
            end = i;
        mx = max(mx, a[i]);
    }
    mx++;
    a[end] = mx;
    vector<vi> pos(mx + 1);
    for (int i = 1; i <= n; i++)
    {
        pos[a[i]].push_back(i);
    }
    vector<vi> adj(n + 1);
    vi deg(n + 1);
    auto addEdge = [&](int u, int v)
    {
        adj[u].push_back(v);
        deg[v]++;
    };
    for (int i = mx - 1; i >= 1; i--)
    {
        // 1.同层之间进行建边
        // 打表发现,同层之间存在次序关系
        // 如果比上一层的一个数所在位置x小,则奇数层为递减关系,偶数层为递增关系
        // 如果比上一层的一个数所在位置x大,则奇数层为递增关系,偶数层为递减关系
        int x = pos[i + 1][0];
        int p = upper_bound(pos[i].begin(), pos[i].end(), x) - pos[i].begin();
        // 小于 x 的区域
        for (int j = 1; j < p; j++)
        {
            int u = pos[i][j - 1];
            int v = pos[i][j];
            // 偶数层为递减、奇数层为递增
            if (i & 1)
                swap(u, v);
            addEdge(u, v);
        }
        // 大于 x 的区域
        for (int j = p + 1; j < (int)pos[i].size(); j++)
        {
            int u = pos[i][j - 1];
            int v = pos[i][j];
            // 奇数层为递减、偶数层为递增
            if (i & 1)
                swap(u, v);
            addEdge(v, u);
        }

        // 2.不同层进行合并
        // 每次遍历完一层都需要向下一层合并(合并 pos[i+1] 与 pos[i] 到 pos[i])
        vi temp(pos[i + 1].size() + pos[i].size());
        int idx = 0, p1 = 0, p2 = 0;
        while (p1 < (int)pos[i + 1].size() && p2 < (int)pos[i].size())
        {
            if (pos[i + 1][p1] < pos[i][p2])
                temp[idx++] = pos[i + 1][p1++];
            else
                temp[idx++] = pos[i][p2++];
        }
        while (p1 < (int)pos[i + 1].size())
            temp[idx++] = pos[i + 1][p1++];
        while (p2 < (int)pos[i].size())
            temp[idx++] = pos[i][p2++];
        pos[i].swap(temp);
        temp.clear();

        // 3. 不同层建边
        // 根据新层 i+1 与 i 的奇偶性
        for (int j = 0; j < (int)pos[i + 1].size(); j++)
        {
            int u = pos[i + 1][j];
            int r = upper_bound(pos[i].begin(), pos[i].end(), u) - pos[i].begin();
            int l = lower_bound(pos[i].begin(), pos[i].end(), u) - pos[i].begin() - 1;
            if (r < (int)pos[i].size())
            {
                int v = pos[i][r];
                if (i & 1)
                    addEdge(u, v);
                else
                    addEdge(v, u);
            }
            if (l >= 0)
            {
                int v = pos[i][l];
                if (i & 1)
                    addEdge(u, v);
                else
                    addEdge(v, u);
            }
        }
        pos[i + 1].clear();
    }

    // 计算拓扑序
    queue<int> q;
    int now = 1;
    vi p(n + 1);
    for (int i = 1; i <= n; i++)
    {
        if (deg[i] == 0)
        {
            q.push(i);
            p[i] = now++;
        }
    }
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        for (auto v : adj[u])
        {
            deg[v]--;
            if (deg[v] == 0)
            {
                q.push(v);
                p[v] = now++;
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        cout << p[i] << " \n"[i == n];
    }
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:
posted @ 2025-04-22 04:15  ExtractStars  阅读(72)  评论(0)    收藏  举报