[九省联考 2018] IIIDX 题解
先简化一下题意,把下取整之类的皮给先剥了,问题转化成给你一棵树,要求给树的每个节点分配权值,使树满足小根堆性质的情况下字典序最大(给定的 \(d\) 只影响树的形态,不理解为什么专门要给一个 \(k\) 为整数的部分分)。
发现有很多的部分分给到了权值互不相等,我们考虑权值互不相等怎么做,不妨再特化一点,给一个二叉堆分配一个排列,看情况会是怎么样的。
首先 \(1\) 节点值固定后考虑 \(2\) 节点的权值,通过堆性质我们可以知道 \(2\) 节点的子树里任意一点的权值都是要比 \(2\) 节点的权值大的,那么因为贪心使 \(2\) 节点最大,就把最后 \(\mathrm{size}_2\) 个权值分配给 \(2\) 的儿子,然后发现这变成了一个递归问题,对以 \(2\) 为根的子树分配权值,有限分配 \(4\),等 \(4\) 分配完再考虑 \(5\),等到 \(2\) 考虑完然后再考虑 \(3\)。
对于多叉树,我们发现这个问题并没有什么变化,对于每个点,按顺序依次处理它的每个子树,递归解决。
这样就可以拿到 \(55\) 的分数,考虑再权值有相等情况下为什么会错。
考虑这样一组数据:
3 3.0
1 1 2
(\(0\) 是作的虚拟节点)
此时如果按照刚刚的方法分配权值,我们会为 \(3\) 分配 \(2\),为 \(2\) 分配 \(1\),这显然是不优的,我们思考是什么让我们的贪心失败了。
整体来看我们的贪心处理过程,我们将自己的权值分配好以后顺序遍历所有的儿子进行递归处理,整个是一个前序遍历的过程,但是考虑我们的要求是字典序最小,在树中应该进行的是类似 bfs 遍历的方法,我们的贪心过程从根本上没有满足题目要求,所以我们应当使用另一种贪心的思路。
在上面的贪心中,我们每处理完一个点,将它的子树权值分配固定,然后再去处理它的兄弟,这次我们不这样做,首先我们将这个点的权值固定,然后对于我们先不去分配它的子树,将它子树需要的权值先“代为保管”,那么我们对于这个“保管”需要满足什么要求呢,当然是之后得还得起,即对于一个分配了权值 \(x\) 的点 \(p\) 而言,在处理到它的儿子之前,小于等于 \(x\) 的数的数量不能少于 \(\mathrm{size}_p\)。
具体怎么维护呢,首先定义 \(c_i\) 表示当前小于等于 \(i\) 的数的个数,也就是一个子树的根节点的权值为 \(i\) 的情况下它的子树最多有多大,我们可以离散化完用线段树维护,当我们做到点 \(p\) 时,考虑如果 \(p\) 的权值为 \(x\),则我们会“保管”成什么样,对于所有 \(i\) 满足 \(i\ge x\),对应的 \(c_i\gets c_i-\mathrm{size}_p\),考虑 \(c\) 数组的意思就可以理解了,我们对于子孙里每个值到底是几还没有确定,但我们知道要“保管” \(\mathrm{size}_p\) 个小于等于 \(x\) 的数,那么我们找到的 \(x\) 肯定要满足 \(\forall i\ge x,\ c_i\ge\mathrm{size}_p\),从 \(c\) 数组的意思理解,这个减掉是“保管”的,如果现在有一个 \(c_i\) 为负,到时候这个 \(c_i\) 要还就还不上了,所以一定要满足这个要求,且对于每个 \(p\),处理到它的儿子时应当把它的影响消去,因为 \(p\) 是给它儿子“保管”的,现在它儿子来了肯定就要还了。
具体代码实现上难度不大。
c++ 代码
#include<bits/stdc++.h>
using namespace std;
#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second
template<typename Ty>
Reimu clear(Ty &x) { Ty().swap(x); }
const int N = 500010;
int n, m;
double d;
int a[N], h[N], fa[N], c[N], sz[N], tr[N], ans[N];
struct SegTree {
int mn[N << 2], tag[N << 2];
Reimu update(int p, int delta) { mn[p] += delta; tag[p] += delta; }
Reimu pushDown(int p) { if (tag[p]) update(p << 1, tag[p]), update(p << 1 | 1, tag[p]), tag[p] = 0; }
Reimu init(int p = 1, int l = 1, int r = m) {
if (l == r) return void(mn[p] = c[l]);
int mid = l + r >> 1, lp = p << 1, rp = lp | 1;
init(lp, l, mid); init(rp, mid + 1, r);
mn[p] = min(mn[lp], mn[rp]);
}
Reimu modify(int L, int R, int delta, int p = 1, int l = 1, int r = m) {
if (L <= l && R >= r) return update(p, delta);
int mid = l + r >> 1, lp = p << 1, rp = lp | 1; pushDown(p);
if (L <= mid) modify(L, R, delta, lp, l, mid);
if (R > mid) modify(L, R, delta, rp, mid + 1, r);
mn[p] = min(mn[lp], mn[rp]);
}
Marisa query(int x, int p = 1, int l = 1, int r = m) {
if (l == r) return l + (mn[p] < x);
int mid = l + r >> 1, lp = p << 1, rp = lp | 1; pushDown(p);
return mn[rp] < x ? query(x, rp, mid + 1, r) : query(x, lp, l, mid);
}
} sgt;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> n >> d;
for (int i = 1; i <= n; ++i) cin >> a[i];
sort(a + 1, a + n + 1, greater<>());
memcpy(h + 1, a + 1, n << 2); m = unique(h + 1, h + n + 1) - h - 1;
for (int i = 1; i <= n; ++i) ++c[lower_bound(h + 1, h + m + 1, a[i], greater<>()) - h];
for (int i = 2; i <= m; ++i) c[i] += c[i - 1];
sgt.init();
for (int i = n; i; --i) sz[fa[i] = i / d] += ++sz[i];
for (int i = 1; i <= n; ++i) {
if (fa[i] ^ fa[i - 1]) sgt.modify(ans[fa[i]], n, sz[fa[i]] - 1);
sgt.modify(ans[i] = sgt.query(sz[i]), n, -sz[i]);
cout << h[ans[i]] << ' ';
}
return 0;
}