CF1404C 题解

· · 题解

第一步很容易想到,把原数与其下标作差,即 a_i=i-a_i

然后可以发现每次可以把值为 0 的数去掉,去掉后其后面所有数都减去 1。并且值为负数的数是永远删不掉的。

关于删的顺序问题,尝试几种情况后容易发现:a_i\ge 0 时,若前面被删掉的数的个数 \ge a_ia_i 总能被删掉。(先删后面不会影响前面,所以一定存在某种顺序能把 a_i 删掉)。举个例子:作差后的序列为 0,1,2,0,1,3,5,删除的下标顺序为 4,5,1,6,2,7,3

这样就不需要考虑删的具体顺序了。

对每个询问,前后各有若干位置不能被删,这就让问题变得恶心了。

如果只固定后缀位置,从头开始考虑,那还是容易模拟的。

现在起点不唯一了,一种解决的方法是:对所有询问离线处理(按照右端点排序),每次考虑到一个新的数 a_r 时,用某个数据结构同时维护 [f_1,f_2,\cdots,f_n],其中 f_i 表示以 i 为起点时能删掉的数的个数。

我们需要用数据结构找到这个位置 $k$ ,然后把区间 $[1,k]$ 都加 $1$ 。 可以用树状数组来实现,$[1,k]$ 区间加 $1$ ,对应差分数组是 $d_1$ 加 $1$ ,$d_{k+1}$ 减 $1$ 。每次都是 $d_1$ 加 $1$ ,那干脆只维护每个位置减 $1$ 的个数好了。那么怎么找 $k+1$ 呢?当前总共加的次数是 $r-1$ ,那么减的总数最多到 $r-1-a_r$ 还是可以满足 $f_i \ge a_r$ 的,也就是说这个分界点 $k+1$ 在第 $r-1-a_r+1$ 个减一操作的位置。那么用树状数组求第 $K$ 小来找这个位置即可。 参考代码如下: ```c++ #include <bits/stdc++.h> using namespace std; typedef long long LL; const int INF = 0x3f3f3f3f; const LL mod = 1e9 + 7; const int N = 300005; int a[N]; int c[N]; int sum(int x) { int res = 0; while (x) { res += c[x]; x -= x & -x; } return res; } void add(int x, int d, int n) { while (x <= n) { c[x] += d; x += x & -x; } } int kth(int k, int n) { int x = 0; for (int i = 1 << 18; i; i >>= 1) { if (x + i <= n && k - c[x + i] > 0) { x += i; k -= c[x]; } } return x + 1; } struct Node { int x, y, id; } b[N]; bool cmp(Node p1, Node p2) { return p1.y < p2.y; } int ans[N]; int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); a[i] = i - a[i]; } for (int i = 0; i < m; i++) { scanf("%d%d", &b[i].x, &b[i].y); b[i].x = 1 + b[i].x; b[i].y = n - b[i].y; b[i].id = i; } sort(b, b + m, cmp); int pos = 1; for (int i = 0; i < m; i++) { while (pos <= b[i].y) { int k = a[pos] < 0 ? 1 : min(kth(pos - a[pos], n), pos + 1); add(k, 1, n); pos++; } ans[b[i].id] = b[i].y - sum(b[i].x); } for (int i = 0; i < m; i++) { printf("%d\n", ans[i]); } return 0; } ```