题面
分析
60 p t s 60pts 60pts 贪心做法很显然对吧,把它看成一棵树很显然对吧。
不妨将权值从小到大排序。
对于一个点 x x x 的子树,每次取 n − s i z [ x ] + 1 → n n-siz[x]+1\to n n−siz[x]+1→n 的权值即可,这种贪心在权值不相等的情况是可行的。p.s. s i z [ x ] siz[x] siz[x] 为 x x x 的子树大小。
而权值不相等时,这种做法显然有问题。不妨思考一下,对于一个点 x x x,要取第 i i i 小的权值要满足什么条件?对的,要满足 i i i 以及 i i i 右边(即比 a [ i ] a[i] a[i] 大)的权值还没取的个数大于等于 s i z [ x ] siz[x] siz[x]。
令 p r e [ i ] pre[i] pre[i] 为 i i i 以及 i i i 右边的权值还没取的个数。则 p r e [ i ] > = s i z [ x ] pre[i]>=siz[x] pre[i]>=siz[x],而每次我们要找到一个最大的 i i i,当然是用二分。
具体做法
p r e pre pre 数组用线段树维护。
更新 p r e pre pre 时,先找到最大的 i i i 记为 i m a x imax imax,由于我们不知道 i m a x imax imax 之后的到底该怎么取,就将 p r e [ 1 ] − p r e [ i m a x ] pre[1]-pre[imax] pre[1]−pre[imax] 之间的权值全部减去 s i z [ x ] siz[x] siz[x] 即可。 (为了给子树的其他节点留位置)
找 i m a x imax imax 时,在线段树上二分,找到最右边的满足 p r e [ 1 ] → p r e [ i m a x ] pre[1]\to pre[imax] pre[1]→pre[imax] 都大于等于 s i z [ x ] siz[x] siz[x],但若多个数字相同,要选最左边的。(贪心)
还有要注意的一点,我们寻找 x x x 的答案时,要把 f a [ x ] fa[x] fa[x] 给子树留的位置全部删除。(p.s.这里删一次即可,因为 f a [ x ] fa[x] fa[x] 是单调递增的)
Code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#include <vector>
using namespace std;
const int MAXN = 5e5 + 5;
struct Segment_Tree {
int L, R, Min, add;
}tree[MAXN << 2];
int n, a[MAXN], cnt[MAXN], ans[MAXN], fa[MAXN], Siz[MAXN], mp[MAXN];
double k;
vector <int> v[MAXN];
int dfs(int x) {
Siz[x] = 1;
for(unsigned int i = 0; i < v[x].size(); i ++) Siz[x] += dfs(v[x][i]);
return Siz[x];
}
int Min(int x, int y) { return x < y ? x : y; }
void Build(int p, int l, int r) {
tree[p].L = l; tree[p].R = r;
if(l == r) { tree[p].Min = n - l + 1; return; }
int mid = (l + r) >> 1;
Build(p << 1, l, mid); Build(p << 1 | 1, mid + 1, r); tree[p].Min = Min(tree[p << 1].Min, tree[p << 1 | 1].Min);
}
void Speard(int p) {
if(tree[p].add) {
tree[p << 1].Min += tree[p].add; tree[p << 1 | 1].Min += tree[p].add;
tree[p << 1].add += tree[p].add; tree[p << 1 | 1].add += tree[p].add;
tree[p].add = 0;
}
}
void Change(int p, int l, int r, int v) {
if(tree[p].L >= l && tree[p].R <= r) { tree[p].Min += v; tree[p].add += v; return; }
Speard(p);
int mid = (tree[p].L + tree[p].R) >> 1;
if(mid >= l) Change(p << 1, l, r, v);
if(mid < r) Change(p << 1 | 1, l, r, v);
tree[p].Min = Min(tree[p << 1].Min, tree[p << 1 | 1].Min);
}
int ask(int p, int v) {
// printf("|%d %d %d %d|\n", tree[p].L, tree[p].R, tree[p].Min, v);
if(tree[p].L == tree[p].R) return (tree[p].Min >= v ? tree[p].L : tree[p].L - 1);
Speard(p);
if(tree[p << 1].Min >= v) return ask(p << 1 | 1, v);
return ask(p << 1, v);
}
int main() {
scanf("%d%lf", &n, &k);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), mp[i] = i;
sort(a + 1, a + 1 + n); a[0] = 0x3f3f3f3f;
for(int i = 1; i <= n; i ++) a[i] == a[i - 1] ? cnt[i] = cnt[i - 1] : cnt[i] = i; // 找这个数值最左边的位置
for(int i = 1; i <= n; i ++) v[(int)floor(i / k)].push_back(i), fa[i] = (int)floor(i / k);
for(int i = 1; i <= n; i ++) if(!Siz[i]) Siz[i] = dfs(i);
Build(1, 1, n);
for(int i = 1; i <= n; i ++) {
if(fa[i] != fa[i - 1] && fa[i]) Change(1, 1, ans[fa[i]], Siz[fa[i]] - 1);
int t = ask(1, Siz[i]); ans[i] = mp[cnt[t]]; mp[cnt[t]] ++;
Change(1, 1, ans[i], -Siz[i]);
}
for(int i = 1; i <= n; i ++) printf("%d ", a[ans[i]]);
return 0;
}