CF1701D Permutation Restoration
题意简述
Monocarp 有一个由 \(n\) 个数字 \(1, 2, \cdots, n\) 组成的排列 \(a\)。
然后 Monocarp 计算出了一个由 \(n\) 个数字组成的数列 \(b\),满足 \(b_i = \lfloor \dfrac{i}{a_i} \rfloor\)。
现在 Monocarp 丢掉了排列 \(a\),他希望你可以通过数列 \(b\) 倒推出排列 \(a\),如果有多个答案,给出任意一个即可,数据保证有解。
多测,\(1 \le T \le 10^5, \sum{n} \le 5 \times 10^5\)
问题分析
由于有 \(b_i = \lfloor \dfrac{i}{a_i} \rfloor\),可以得到 \(b_i \le \dfrac{i}{a_i} < b_i+1\),即 \(\dfrac{i}{b_i+1} < a_i \le \dfrac{i}{b_i}\)。
枚举排列 \(a\) 中每个数,假设现在枚举到 \(x\)。对于每个 \(x\),存在一些线段 \(S_i\),满足 \(\dfrac{i}{b_i+1} < x \le \dfrac{i}{b_i}\)。
从贪心的角度来说,对于这些线段,我们选择右端点最小的线段,为其他空留下更多的选择空间。
考虑类似于尺取的方法,从 \(1\) 枚举到 \(n\),当枚举到 \(x\) 的时候,将所有以 \(x\) 为左端点的线段加入到一个以右端点为排序关键字的优先队列中,每次选取最小的即可。
代码实现
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template < typename Tp >
void read(Tp &x) {
x = 0; int fh = 1; char ch = 1;
while(ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if(ch == '-') fh = -1, ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= fh;
}
const int maxn = 500000 + 7;
int T, n, b[maxn];
void Init(void) {
read(T);
}
pair <int, pair<int, int> > pr[maxn];
priority_queue < pair<int, int> > Q;
int ans[maxn];
void Work(void) {
while(T--) {
for(int i = 1; i <= n; i++) {
ans[i] = 0; pr[i].first = pr[i].second.first = pr[i].second.second = 0;
}
read(n);
// memset(ans, 0, sizeof(ans));
// while(!Q.empty()) Q.pop();
// memset(pr, 0, sizeof(pr));
for(int i = 1; i <= n; i++) {
read(b[i]);
pr[i].first = i / (b[i] + 1) + 1;
if(b[i] == 0) pr[i].second.first = n;
else pr[i].second.first = i / b[i];
pr[i].second.second = i;
}
sort(pr + 1, pr + n + 1);
int cnt = 0;
for(int i = 1; i <= n; i++) {
while(cnt <= n && pr[cnt + 1].first == i) {
Q.push(make_pair(-pr[cnt + 1].second.first, pr[cnt + 1].second.second));
cnt++;
}
int id = Q.top().second; Q.pop();
ans[id] = i;
}
for(int i = 1; i <= n; i++) {
printf("%d%c", ans[i], " \n"[i == n]);
}
}
}
int main(void) {
Init();
Work();
return 0;
}