Codeforces Round 1019 div2 个人题解(2103D)

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

题目翻译

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

  • 2 ≤ i ≤ m − 1 2\le i\le m - 1 2im1 b i < b i − 1 b_i \lt b_{i - 1} bi<bi1 b i < b i + 1 b_i \lt b_{i + 1} bi<bi+1 ,或
  • i = 1 i = 1 i=1 b 1 < b 2 b_1 \lt b_2 b1<b2 ,或
  • i = m i = m i=m b m < b m − 1 b_m \lt b_{m - 1} bm<bm1

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

  • 2 ≤ i ≤ m − 1 2\le i\le m - 1 2im1 b i > b i − 1 b_i \gt b_{i - 1} bi>bi1 b i > b i + 1 b_i \gt b_{i + 1} bi>bi+1 ,或
  • i = 1 i = 1 i=1 b 1 > b 2 b_1 \gt b_2 b1>b2 ,或
  • i = m i = m i=m b m > b m − 1 b_m \gt b_{m - 1} bm>bm1

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

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

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

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

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

可以证明, p p p 中最多经过 ⌈ log ⁡ 2 n ⌉ \lceil \log_2 n\rceil log2n 次迭代(换句话说, a i ≤ ⌈ log ⁡ 2 n ⌉ a_i \le \lceil \log_2 n\rceil ailog2n 次迭代)就只剩下一个元素。

给你一个数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,,an 。你的任务是构造出满足数组 a a a n n n 元素的任意排列 p p p

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

输入

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

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

每个测试用例的第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,,an ( 1 ≤ a i ≤ ⌈ log ⁡ 2 n ⌉ 1 \le a_i \le \lceil\log_2 n\rceil 1ailog2n a i = − 1 a_i = -1 ai=1 )–元素 p i p_i pi 被移除的迭代次数。

保证所有测试用例中 n n n 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2105

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

输出

对于每个测试用例,输出 n n n 个整数,这些整数代表满足排列数组 a a 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 ] [3, 2, 1] [3,2,1] 进行如下操作:

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

这满足数组 a = [ 1 , 1 , − 1 ] a = [1, 1, -1] a=[1,1,1] 的要求,因为 p 1 p_1 p1 p 2 p_2 p2 都在迭代次数 1 1 1 中被删除,而 p 3 p_3 p3 没有被删除。

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

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

由于元素 p 1 = 4 p_1 = 4 p1=4 p 3 = 5 p_3 = 5 p3=5 p 5 = 2 p_5 = 2 p5=2 在迭代 1 1 1 中被删除,元素 p 4 = 1 p_4 = 1 p4=1 在迭代 2 2 2 中被删除,元素 p 2 = 3 p_2 = 3 p2=3 没有被删除,因此满足数组 a = [ 1 , − 1 , 1 , 2 , 1 ] a = [1, -1, 1, 2, 1] a=[1,1,1,2,1] 的要求。

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

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

在第四个测试用例中,一个满足约束条件的排列是:[ 6 6 6 , 5 5 5 , 2 2 2 , 1 1 1 , 3 3 3 , 4 4 4 , 7 7 7 }。 1 1 1 是唯一的局部最小值,因此只有它能在第一次迭代后保持不变。请注意,还有其他有效的排列;例如,[ 6 6 6 , 4 4 4 , 3 3 3 , 1 1 1 , 2 2 2 , 5 5 5 , 7 7 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######################################*/
// 链接:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值