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]\) 进行如下操作:
- \([3, 2, 1]\) 中唯一的局部最小值是 \(1\) 。因此,移除元素 \(3\) 和 \(2\) 。只剩下一个元素,因此过程结束。
这满足数组 \(a = [1, 1, -1]\) 的要求,因为 \(p_1\) 和 \(p_2\) 都在迭代次数 \(1\) 中被删除,而 \(p_3\) 没有被删除。
在第二个测试案例中,将对排列 \(p = [4, 3, 5, 1, 2]\) 进行如下操作:
- \([4, 3, 5, 1, 2]\) 中的局部最小值是 \(3\) 和 \(1\) 。因此,删除元素 \(4\) 、 \(5\) 和 \(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]\) 进行如下操作:
- \([6, 7, 2, 4, 3, 8, 5, 1]\) 中的局部最小值是 \(6\) 、 \(2\) 、 \(3\) 和 \(1\) 。因此,删除元素 \(7\) 、 \(4\) 、 \(8\) 和 \(5\) 。
- \([6, 2, 3, 1]\) 中的局部最大值是 \(6\) 和 \(3\) 。因此,元素 \(2\) 和 \(1\) 被删除。
- \([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######################################*/
// 链接: