[P3863] 序列 题解

[P3863] 序列 题解

题目描述

给定一个长度为 \(n\) 的序列,给出 \(q\) 个操作,形如:

\(1~l~r~x\) 表示将序列下标介于 \([l,r]\) 的元素加上 \(x\) (请注意,\(x\) 可能为负)

\(2~p~y\) 表示查询 \(a_p\) 在过去的多少秒时间内不小于 \(y\) (不包括这一秒,细节请参照样例)

开始时为第 \(0\) 秒,第 \(i\) 个操作发生在第 \(i\) 秒。

对于 \(100\%\) 的数据,保证 \(2 \leq n,q \leq 100000\)\(1 \leq l \leq r \leq n\)\(1 \leq p \leq n\)\(-10^9 \leq x,y,a_i \leq 10^9\)

思路

先思考一个弱化版:\(n = 1\) 时,进行修改和询问。

那么这个问题就变为了一个下标为时间的序列,序列元素值为当前时间(下标)时,\(a_1\) 的值。

这个是分块的经典应用,当 \(n, q\) 同阶,可以在 \(O(n\sqrt n\log(\sqrt n))\) 解决此问题,参考 数列分块入门 2

现在把问题拓展,\(n > 1\)

如果以时间为 \(x\) 轴,序列元素为 \(y\) 轴,建立平面直角坐标系,用数据结构维护序列,那么,每个操作影响的区间如下图所示:

image

如图,如果我们按时间顺序去处理询问,会发现询问影响到的部分包括了之前的时间,这是很棘手的。

不过我们把屏幕旋转90°:

image

就会发现,如果以序列元素为 \(y\) 轴,时间为 \(x\) 轴,用数据结构维护时间,那么查询就变成了在数据结构上的区间查询。

哎呀,糟糕,现在修改变成了跨越多个 \(x\) 的棘手问题了,这该怎么办!

实际上,这个可以利用差分的思想解决,即在遍历到第 \(l\) 个序列下标时加上修改的影响,在遍历到第 \(r + 1\) 序列下标时消去这次修改的贡献。

image

至于序列初始值,可以在查询的时候减去它,也可以作为操作挂在一开始。

当然,这个流程也可以理解为扫描线算法。

实现

离线,按序列下标排序,这个可以用 vector 把这些操作挂在序列下标上实现,然后用分块维护时间的序列。

总时间复杂度:\(O(n\sqrt n\log_2(\sqrt n) )\)

// Problem: P3863 序列
// Author: Moyou
// Copyright (c) 2023 Moyou All rights reserved.
// Date: 2023-05-20 08:47:18

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
#define speedup (ios::sync_with_stdio(0), cin.tie(0), cout.tie(0))
#define int long long
#define get(i) ((i - 1) / len + 1)

using namespace std;

const int N = 2e5 + 10, M = 450;

int a[N];
int bk[N], b[N], L[M], R[M], tag[M], len, tot; 
// 单点修改,区间查询多少个大于等于y
int n, T;

struct Options
{
    int op;
    int t, v;
} ;

vector<Options> q[N];

void reset(int k)
{
    for(int i = L[k]; i <= R[k]; i ++)
        b[i] = bk[i];
    sort(b + L[k], b + R[k] + 1);
}

void update(int p, int v)
{
    int l = p, r = T;
    if(get(l) == get(r))
    {
        for(int i = l; i <= r; i ++)
             bk[i] += v;
        reset(get(l));
    }
    else
    {
        for(int i = l; i <= R[get(l)]; i ++)
            bk[i] += v;
        reset(get(l));
        for(int i = L[get(r)]; i <= r; i ++)
            bk[i] += v;
        reset(get(r));
        for(int i = get(l) + 1; i <= get(r) - 1; i ++)
            tag[i] += v;
    }
    reset(get(p));
}

int query(int p, int x)
// 1 ~ p 中多少数>= x
{
    int l = 1, r = p, res = 0;
    if(get(l) == get(r))
    {
        for(int i = l; i <= r; i ++)
            if(bk[i] + tag[get(i)] >= x) res ++;
    }
    else
    {
        for(int i = l; i <= R[get(l)]; i ++)
            if(bk[i] + tag[get(i)] >= x) res ++;
        for(int i = L[get(r)]; i <= r; i ++)
            if(bk[i] + tag[get(i)] >= x) res ++;
        for(int i = get(l) + 1; i <= get(r) - 1; i ++)
        {
            int t = lower_bound(b + L[i], b + R[i] + 1, x - tag[i]) - b;
            if(b[t] >= x - tag[i]) res += R[i] - t + 1;
        }
    }
    return res;
}

int ans[N];

signed main()
{
    speedup;
    memset(ans, -1, sizeof ans);
    cin >> n >> T;
    for(int i = 1; i <= n; i ++)
        cin >> a[i];
    for(int i = 2, op, x, y, t; i <= T + 1; i ++) // 时间轴右移,给0时留出位置
    {
        cin >> op >> x >> y;
        if(op == 1)
        {
            cin >> t;
            q[x].push_back({op, i, t});
            q[y + 1].push_back({op, i, -t});
        }
        else
            q[x].push_back({op, i, y});
    }
         
    T ++;
    len = sqrt(T);
    tot = (T - 1) / len + 1;
    for(int i = 1; i <= tot; i ++)
        L[i] = (i - 1) * len + 1, R[i] = i * len;
    
    for(int i = 1; i <= n; i ++)
    {
        for(auto [op, tm, x] : q[i])
        {
            if(op == 1)
                update(tm, x);
            else
                ans[tm] = query(tm - 1, x - a[i]);
        }
    }
    
    for(int i = 1; i <= T; i ++)
        if(~ans[i]) cout << ans[i] << '\n';

    return 0;
}

posted @ 2023-05-21 00:30  MoyouSayuki  阅读(9)  评论(0编辑  收藏  举报
:name :name