Address
Solution
被这道题虐了两天
由于
,所以难度的拓扑序是一棵树或森林。
具体地,我们把
(如果存在)作为
的父亲节点。
问题转化为将
数组填充进节点的权值,使得每个点子树内的权值都不小于这个点的权值,并输出字典序最大的方案。
字典序显然可以贪心。
考虑记下子树大小
,并将
从大到小排序。
一个优 (cuo) 秀 (wu) 的贪心:
节点
为根,那么节点
及其子树内分配到
的权值。
如果
为根,那么
及其子树内分配到
的权值。
如果
还是根,则其子树分配到
,以此类推。
如果
有父亲
且
预分配到了
,就同样把
如上面一样分组并分配给
。
才怪。 55 分。
考虑
有相同权值的时候,如:
那么这是由两棵树构成的森林,一棵大小为
另一棵为
。
显然,第一棵树要分配权值的最小值为
,即节点
要填充
。
然而如果这时第一棵树不填充
而填充
就可以把剩下的
让给第二棵树,得到字典序更大的解。
于是我们改变贪心策略:在
中从右往左找到最左位置
使得节点
的左边(包括
)至少存在
个未被取走的节点,然后找到一个
使得满足
且
最大。这时候节点
填充的权值为
。
考虑用线段树。设
表示
的左边还有多少个点可取。
计算一个节点
时,可以得出如果
的权值能取
,那么一定满足:
可以在线段树上二分得到这个最小的
。需要记录区间最小值。
然后找到一个
满足
,
没有被取走并且
最大。
然后把
的权值设为
,这时候,
都要减去
。维护标记即可。这可以看成是为
的子树预留
个权值。
同时注意细节:如果一个点
有父亲
,那么这时候
就可 (bi) 以 (xu) 放在
的子树内(废话),所以要把
为子树所预留的点(不包括
自己)还给
以及
的其他子节点,也就是
减去
。特别注意
只需要在第一个子节点
处消除影响一次。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define p2 p << 1
#define p3 p << 1 | 1
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 5e5 + 5, M = N << 2;
int n, d[N], fa[N], sze[N], f[M], add[M], pre[N], nxt[N], ans[N];
double k;
int comp(int a, int b) {
return a > b;
}
void build(int l, int r, int p) {
if (l == r) return (void) (f[p] = l);
int mid = l + r >> 1;
build(l, mid, p2); build(mid + 1, r, p3);
f[p] = min(f[p2], f[p3]);
}
void down(int p) {
add[p2] += add[p]; add[p3] += add[p];
add[p] = 0;
}
void upt(int p) {
f[p] = min(f[p2] + add[p2], f[p3] + add[p3]);
}
void change(int l, int r, int s, int e, int v, int p) {
if (l == s && r == e) return (void) (add[p] += v);
int mid = l + r >> 1;
down(p);
if (e <= mid) change(l, mid, s, e, v, p2);
else if (s >= mid + 1) change(mid + 1, r, s, e, v, p3);
else change(l, mid, s, mid, v, p2),
change(mid + 1, r, mid + 1, e, v, p3);
upt(p);
}
int ask(int l, int r, int k, int p) {
if (l == r) return f[p] + add[p] >= k ? l : l + 1;
int mid = l + r >> 1, res;
down(p);
if (f[p3] + add[p3] >= k) res = ask(l, mid, k, p2);
else res = ask(mid + 1, r, k, p3);
return upt(p), res;
}
int main() {
int i;
cin >> n >> k;
For (i, 1, n) d[i] = read();
sort(d + 1, d + n + 1, comp);
For (i, 1, n) fa[i] = floor(1.0 * i / k);
For (i, 1, n) {
pre[i] = i;
if (i > 1 && d[i - 1] == d[i]) pre[i] = pre[i - 1];
}
Rof (i, n, 1) {
nxt[i] = i;
if (i < n && d[i] == d[i + 1]) nxt[i] = nxt[i + 1];
}
For (i, 1, n) sze[i] = 1;
Rof (i, n, 1) if (fa[i]) sze[fa[i]] += sze[i];
build(1, n, 1);
For (i, 1, n) {
if (fa[i] && fa[i] != fa[i - 1])
change(1, n, ans[fa[i]], n, sze[fa[i]] - 1, 1);
int pos = ask(1, n, sze[i], 1), p = pre[pos], q = nxt[p];
nxt[p]--; ans[i] = q;
change(1, n, q, n, -sze[i], 1);
}
For (i, 1, n) printf("%d ", d[ans[i]]);
cout << endl;
return 0;
}