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 1≤i≤m ) 是局部最小值,如果以下条件至少有一个成立:
- 2 ≤ i ≤ m − 1 2\le i\le m - 1 2≤i≤m−1 和 b i < b i − 1 b_i \lt b_{i - 1} bi<bi−1 和 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<bm−1 。
同样地,如果以下条件至少有一个成立,那么数组 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 1≤i≤m )就是局部最大值:
- 2 ≤ i ≤ m − 1 2\le i\le m - 1 2≤i≤m−1 、 b i > b i − 1 b_i \gt b_{i - 1} bi>bi−1 和 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>bm−1 。
请注意,只有一个元素的数组不定义局部最小值和最大值。
有一个隐藏的 排列 ∗ \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 1≤i≤n ),设 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 ai≤⌈log2n⌉ 次迭代)就只剩下一个元素。
给你一个数组 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 1≤t≤104 )。测试用例说明如下。
每个测试用例的第一行都包含一个整数 n n n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 2 \le n \le 2 \cdot 10^5 2≤n≤2⋅105 )–排列 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 1≤ai≤⌈log2n⌉ 或 a i = − 1 a_i = -1 ai=−1 )–元素 p i p_i pi 被移除的迭代次数。
保证所有测试用例中 n n n 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105 。
保证至少有一种排列 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] 进行如下操作:
- [ 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] 进行如下操作:
- [ 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 。
- [ 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] 进行如下操作:
- [ 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 。
- [ 6 , 2 , 3 , 1 ] [6, 2, 3, 1] [6,2,3,1] 中的局部最大值是 6 6 6 和 3 3 3 。因此,元素 2 2 2 和 1 1 1 被删除。
- [ 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######################################*/
// 链接: