IOI 2015 Teams 分组
贪心做法
对于每组询问, 我们从小到大考虑 K
,那么每次肯定是在 A
小于等于 K
的 $ (A, B) $ 中选取最小的 K
个 B
,因为更大的 B
可能后面会用到。
因此每次直接暴力可以得到 $ O(N * S * \log N) $ 的算法(先按 A
排序,然后维护 B
的值,可以考虑树状数组)。
优化之后的做法
我们转换一下题目,对于可以被 K
选择的 $ (A, B) $ ,因为要满足 $ A \le K $ 且 $ K \le B $ ,所以在坐标系中,点 $ (A, B) $ 就要在点 $ (K, K) $ 的左上方。所以对于某个 K
,满足 $ x \in [0, K] , y \in [K, + \infin) $ 的点是可以被其选择的,注意这些点都是在一个矩形中。
但是我们不能直接用所有满足条件的点,因为可能有的点已经被前面的矩形用过了!
对于当前的矩形,他一定会用掉能选的点中,纵坐标最小的 $ K_i $ 个点,记没被用掉的点中最小的纵坐标是 $ H_i $ (注意这里的 $ H_i $ 是指自己到上个矩形的范围内,因为自己所维护的矩形是这一段)。
(在下图中,虽然 A
是 j
可选但是没有选择的点中最低的,但是 B
的高度才是 $ H_j $ )
那么对于一个 $ K_j \ge K_i $ 的 $ K_j $ ,如果 $ K_j > H_i $ ,那么由于可以被 $ j $ 使用的点 $ i $ 都没有用过,所以 $ j $ 可以直接使用;但如果 $ k_j \le H_i $ ,那么可能某些点(注意存在和 $ H_i $ 高度相同的点)已经被 $ i $ 用过了,所以就不能直接计数。
但是我们可以维护出 $ i $ 还没有用过的点的数量 $ rem_i $ ,这样由于 $ K_j < H_i $ ,所以 $ rem_i $ 这些点 $ j $ 也是可以直接用的,再加上横坐标在 $ (H_i, H_j] $ 之间的 $ j $ 可以使用的点,就是全部 $ j $ 可以使用的点。
注意到我们处理的 K
是单调不降的,所以可以用一个单调栈维护一个 H
单调递减的序列,每次处理之前,把 H
小于当前高度的矩形都出栈(注意只有小于的可以出栈),然后再通过前面的 rem
以及新的点数来判断是否可行并且维护。
因为这是一个二维平面上的计数问题,所以可以用主席树维护点的数量。
代码
int Rt[N], lson[M], rson[M], val[M], cntNode;
void Insert(int &rt, int pre, int l, int r, int x) {
rt = ++cntNode;
lson[rt] = lson[pre], rson[rt] = rson[pre];
val[rt] = val[pre] + 1;
if (l == r) return ;
int mid = (l + r) >> 1;
if (x <= mid) Insert(lson[rt], lson[pre], l, mid, x);
else Insert(rson[rt], rson[pre], mid + 1, r, x);
}
int Query(int L, int R, int l, int r, int k) { // (cnt) >= k
if (l == r) return val[R] - val[L];
int mid = (l + r) >> 1, sum = val[rson[R]] - val[rson[L]];
if (k > mid) return Query(rson[L], rson[R], mid + 1, r, k);
else return sum + Query(lson[L], lson[R], l, mid, k);
}
int Queryk(int L, int R, int l, int r, int k) { // kth
if (l == r) return l;
int mid = (l + r) >> 1, sum = val[rson[R]] - val[rson[L]];
if (k > sum) return Queryk(lson[L], lson[R], l, mid, k - sum);
else return Queryk(rson[L], rson[R], mid + 1, r, k);
}
int n, m, A[N];
struct NODE {
int x, y;
inline int operator < (const NODE &__) const {
return x < __.x || (x == __.x && y < __.y);
}
} node[N];
int stk[N], rem[N], high[N], Top;
int main() {
n = read<int>();
rep (i, 1, n) node[i].x = read<int>(), node[i].y = read<int>();
sort(node + 1, node + n + 1);
int cur = 1;
rep (i, 1, n) {
Rt[i] = Rt[i - 1];
for ( ; cur <= n && node[cur].x == i; ++cur)
Insert(Rt[i], Rt[i], 1, n, node[cur].y);
}
rep (ks, 1, read<int>()) {
rep (i, 1, m = read<int>()) A[i] = read<int>();
sort(A + 1, A + m + 1), Top = 0;
rep (i, 1, m) {
for ( ; high[Top] < A[i] && Top; --Top) ;
int tot = rem[Top]
+ Query(Rt[stk[Top]], Rt[A[i]], 1, n, A[i]) - A[i];
if (tot < 0) { puts("0"); break; }
else if (i == m) { puts("1"); break; }
int H = Queryk(Rt[stk[Top]], Rt[A[i]], 1, n, tot - rem[Top]);
for ( ; H > high[Top] && Top; )
--Top, H = Queryk(Rt[stk[Top]], Rt[A[i]], 1, n, tot - rem[Top]);
stk[++Top] = A[i], rem[Top] = tot, high[Top] = H;
}
}
return 0;
}