嘘~ 正在从服务器偷取页面 . . .

CF1404C Fixed Point Removal 题解


CF1404C Fixed Point Removal 题解

题目链接:CF1404C Fixed Point Removal

题意

\(a_1, \ldots, a_n\) 为一个包含 \(n\) 个正整数的数组。

在一次操作中,你可以选择一个位置 \(i\) 使得 \(a_i = i\),并从数组中移除 \(a_i\)(移除后,剩余部分被拼接起来)。

\(a\) 的权重定义为你能移除的最大元素数量。

你必须回答 \(q\) 个独立的查询 \((x, y)\):在将 \(a\) 的前 \(x\) 个元素和最后 \(y\) 个元素替换为 \(n+1\)(使它们无法被移除)后,\(a\) 的权重是多少?

输入格式

第一行包含两个整数 \(n\)\(q\) \((1 \le n, q \le 3 \cdot 10^5)\)——数组的长度和查询的数量。

第二行包含 \(n\) 个整数 \(a_1\), \(a_2\), ..., \(a_n\) \((1 \leq a_i \leq n)\)——数组的元素。

接下来的 \(q\) 行中的第 \(i\) 行包含两个整数 \(x\)\(y\) \((x, y \ge 0\)\(x+y < n)\)

输出格式

打印 \(q\) 行,第 \(i\) 行应该包含一个单独的整数——第 \(i\) 个查询的答案。

考虑令 \(a_i = i - a_i\) ,这样就只保留了相对距离。

可以发现,当 \(a_i \ge 0\) 且前面被删掉的数的个数 \(\ge a_i\) 时, \(a_i\) 总能被删掉。

于是我们不需要考虑一个可以删除的序列的删除顺序了。

然而询问并不从序列的开头开始,而是询问一个区间的答案,不过看上去不是很好在线维护。(反正我不会)

考虑离线询问,将询问按右端点顺序排序。每考虑一个新的数 \(a_r\) 时,维护 \(f_i\) 表示以 \(i\) 为起点时能删掉的数。

考察 \(a_r\) 的贡献:显然 \(f_i\) 单调递减,设 \(p\) 为最大的满足 \(f_p \ge a_r\) 的位置,则 \(1\)\(p\) 的每个 \(f\) 都能吃到 \(1\) 的贡献

于是我们只需要快速找到这个 \(p\) 即可,考虑用树状数组维护。

注意到每次修改时都是 \(t_1\)\(1\)\(t_{p+1}\)\(1\) ,于是我们可以直接维护每个位置减 \(1\) 的个数。

那么如何找到 \(p+1\) 呢?设当前一共加了 \(r-1\) 次,那么减的次数不会超过 \(r-1-a_r\) 次。

考虑树状数组上二分求第 \(k\) 小(全局),方式和线段树上二分挺类似的,详见代码。

时间复杂度 \(\mathcal{O}(n \log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(3e5 + 15))

int n, a[N], tr[N], ans[N];
struct node { int x, y, id; } b[N];
int lowbit(int x) { return x & (-x); }
void add(int x, int v) { for(int i = x; i <= n; i += lowbit(i)) tr[i] += v; }
int sum(int x, int r = 0) { for(int i = x; i; i -= lowbit(i)) r += tr[i]; return r; }
int kth(int k)
{
    int p = 0;
    for(int i = 1 << 18; i; i >>= 1)
        if(p + i <= n && k - tr[p + i] > 0) { p += i; k -= tr[p]; }
    return p + 1;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int m; cin >> n >> m;
    for(int i = 1; i <= n; i++) { cin >> a[i]; a[i] = i - a[i]; }
    for(int i = 0; i < m; i++)
    {
        cin >> 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, [](node a, node b) { return a.y < b.y; });
    int pos = 1;
    for(int i = 0; i < m; i++)
    {
        for(int k; pos <= b[i].y; ++pos) {
            k = (a[pos] < 0 ? 1 : min(kth(pos - a[pos]), pos + 1)); add(k, 1);
        }
        ans[b[i].id] = b[i].y - sum(b[i].x);
    }
    for(int i = 0; i < m; i++) cout << ans[i] << '\n';
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/x70o5ehy

[2] https://www.cnblogs.com/LiuRunky/p/BIT_Find_kth.html


题外话

感觉对于这种题还是没有将题目规律与数学的直接关系找准,可以算是建模水平还不够吧。


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录