很容易发现,歌曲间的关系构成了一棵树,于是O(n)的贪心算法也就是显然的。
但是我们对贪心带来的55~60分不满意,鉴于这个题是个nlogn范围,所以还是用数据结构吧。
很容易发现,题目给出的n首歌的难度顺序不会影响最终答案,所以首先对这个难度值从大到小排序。
不过树还是要构造的,接下来预处理出每个节点的子树大小。
之后,每次找一个最靠左的位置,使得这个位置左边足够放下当前节点的子树。如果有多个值相同,就放到最右边的值那里。
上面是最关键的一句话,想到这里,就突破了贪心狭隘的思路,并取得了成功的一半。
利用线段树维护每个位置左边(包括自身)还能放多大的子树,初始为1, 2, 3 ... n。
记录一个same数组表示与当前数字相同的值最靠右边的位置,然后每次查找时在线段树上二分,之后再放到最右边那里,记为pos。
放完之后,更新线段树,因为放了一棵树,所以使得pos位置和pos右边的所有位置左边的空间减少了当前节点的子树大小,就对线段树进行区间减操作。
时间复杂度O(nlogn)。
#include <iostream>
#include <algorithm>
#include <cstdio>
const int maxn = 5e5 + 7;
using namespace std;
int a[maxn];
int fa[maxn];
int ans[maxn];
int size[maxn];
int same[maxn];
int st[maxn << 2]; //线段树维护最小值
int add[maxn << 2];
inline int read()
{
int X = 0; char ch = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') X = (X << 3) + (X << 1) + ch - '0', ch = getchar();
return X;
}
inline bool cmp(int x, int y)
{
return x > y;
}
inline void pushup(int rt)
{
st[rt] = min(st[rt << 1], st[rt << 1 | 1]);
}
void build(int l, int r, int rt)
{
if (l == r) {
st[rt] = l; //初始为1 -> n的序列
return;
}
int mid = (l + r) >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
pushup(rt);
}
inline void pushdown(int rt)
{
if (add[rt]) {
st[rt << 1] += add[rt];
st[rt << 1 | 1] += add[rt];
add[rt << 1] += add[rt];
add[rt << 1 | 1] += add[rt];
add[rt] = 0;
}
}
void update(int x, int y, int z, int l, int r, int rt)
{
if (l >= x && r <= y) {
st[rt] += z;
add[rt] += z;
return;
}
pushdown(rt);
int mid = (l + r) >> 1;
if (x <= mid) update(x, y, z, l, mid, rt << 1);
if (y > mid) update(x, y, z, mid + 1, r, rt << 1 | 1);
pushup(rt);
}
int find(int x, int l, int r, int rt) //线段树中的二分查找
{
if (l == r) return l + (st[rt] < x);
pushdown(rt);
int ls = rt << 1, rs = ls | 1, mid = (l + r) >> 1;
if (x <= st[rs]) return find(x, l, mid, ls);
return find(x, mid + 1, r, rs);
}
int main(void)
{
int n = read();
double k;
cin >> k;
for (register int i = 1; i <= n; i++) a[i] = read();
sort(a+1, a+n+1, cmp);
for (register int i = n-1; i; i--) if (a[i] == a[i+1]) same[i] = same[i+1] + 1; //每个数与它右边连续几个数相同
for (register int i = 1; i <= n; i++) fa[i] = int(i / k), size[i] = 1;
for (register int i = n; i; i--) size[fa[i]] += size[i]; //预处理子树大小
build(1, n, 1);
for (register int i = 1; i <= n; i++) {
if (fa[i] != fa[i-1]) update(ans[fa[i]], n, size[fa[i]] - 1, 1, n, 1); //维护单调性以保证二分查找的正确性
int pos = find(size[i], 1, n, 1);
pos += same[pos]; //传送到最右边相同值位置
same[pos]++; //因为一个位置不能重复使用多次,所以第一遍传送到最右值之后,第二遍要传送到次右值,以此类推
pos -= same[pos] - 1; //往回跳
ans[i] = pos;
update(pos, n, -size[i], 1, n, 1); //更新
}
for (register int i = 1; i <= n; i++) printf("%d ", a[ans[i]]);
return 0;
}