BZOJ5249 [多省联考2018] IIIDX
Description
给定一个长为 $n$ 的序列 $d$ (可能有重复)和一个常数 $k$ (不一定是整数)。 求 $d$ 的一个排列,要求满足 $d_i \geqslant d_{\lfloor\frac ik\rfloor}$ 。
求所有满足条件的排列中字典序最大的一个。
Solution
首先,字典序最小的一个就是直接排序一遍(然而没什么用)。
贪心方法...算了我还是直接说正解好了。
假设我们有一个函数叫做 check ,能够在我填完前面一部分的时候告诉我是否存在合法解。
容易证明从前往后填到某一位的时候这一位填的越小越容易有解,所以可以逐位二分。
时间复杂度 $O(n\log n * Check)$ 。
先考虑一个 $O(n)$ 的 check 怎么写。
显然 $1$ 到 $n$ 的顺序同时也是这棵树的 bfs 序,所以不存在一个点填了而它的父亲没填的情况。
考虑把所有填了的点(带着它的子树)从它的父亲上摘下来。
如果现在填了 $k$ 个数,那么就有 $k$ 棵树组成的森林。
可以发现,如果我给每棵树分配一些权值,使得每棵树里分配的权值都不小于这棵树的树根权值,并且个数恰好是这棵树里除根结点外的结点个数, 那么我一定能够找到一种合法的方案,即每棵树按 bfs 序从小到大填即可。
所以说我现在相当于有 $k$ 个盒子,第 $i$ 个盒子有一个最小权值 $t_i$ 和一个大小 $s_i$ ,现在我要将剩余 $n-k$ 个数放到这些盒子里,问是否存在合法方案。
那么根据贪心策略我只需要把盒子按 $t_i$ 排序,然后把 $n-k$ 个数从小到大放到盒子里即可。
这样就可以拿到 60 分(配合贪心有 70 )的好成绩啦!
然后再来考虑如何把 check 加快。
我们令 $b_i$ 表示 i 这个数出现的次数,那么 check 返回 true 当且仅当
对于所有的 $x$ ,都有 $\sum_{i \ge x} b_i \ge \sum_{t_i \ge x} s_i$ 。
把不等式移项,可以得到它是一个后缀和大于零的形式。线段树维护最小后缀和,判断其是否非负即可。
总复杂度 $O(n\log^2n)$
Code
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
inline int readInt() {
int ans = 0, c;
while (!isdigit(c = getchar()));
do ans = ans * 10 + c - '0';
while (isdigit(c = getchar()));
return ans;
}
const int N = 530000;
const int L = N * 20;
char s[L], *p = s;
void print(int x) {
if (x == 0) return;
(x / 10);
print*(p++) = x % 10 + '0';
}
int n, m, A[N], B[N], C[N];
int siz[N], val[N];
double k;
inline int fa(int x) { return (int)(floor(x / k + 1e-5)); }
int len;
int sumv[N * 4], minv[N * 4];
inline void upd(int o) {
[o] = sumv[o << 1] + sumv[o << 1 | 1];
sumv[o] = std::min(minv[o << 1 | 1], minv[o << 1] + sumv[o << 1 | 1]);
minv}
void build() {
for (int i = 0; i < len; ++i) minv[i + len] = sumv[i + len] = B[i + 1];
for (int i = len - 1; i > 0; --i) upd(i);
}
void add(int x, int y) {
int t = B[x] -= y;
+= len - 1;
x [x] = minv[x] = t;
sumvfor (x >>= 1; x > 0; x >>= 1) upd(x);
}
inline bool check() { return minv[1] >= 0; }
inline bool check(int x, int y) {
(y, siz[x]);
addbool ans = check();
(y, -siz[x]);
addreturn ans;
}
int main() {
= readInt();
n ("%lf", &k);
scanffor (int i = 0; i < n; ++i) A[i] = readInt();
std::sort(A, A + n);
for (int i = 0; i < n; ++i) {
++B[(i != 0 && A[i] == A[i - 1]) ? m : ++m];
[m] = A[i];
C}
= 1;
len while (len < m) len <<= 1;
();
buildfor (int i = n; i > 0; --i)
[fa(i)] += ++siz[i];
siz(val[0] = 1, n);
addfor (int i = 1; i <= n; ++i) {
int s = siz[i];
(val[fa(i)], -s);
addint l = val[fa(i)], r = m;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(i, mid)) l = mid;
else r = mid - 1;
}
(val[i] = l, s);
add}
for (int i = 1; i <= n; ++i) {
(C[val[i]]);
print*(p++) = ' ';
}
*(p++) = '\0';
(s);
putsreturn 0;
}