P9993 [Ynoi Easy Round 2024] TEST_133 题解

发布时间 2024-01-02 11:58:39作者: Athanasy

题目链接: [Ynoi Easy Round 2024] TEST_133

首先历史最大加,吉司机跑不掉了,维护历史最大加标记以及历史最大,那么根据吉司机标记思想更新操作应该为

\[new \Leftarrow \max{(h_{max},a_i+h_{addMax})} \]

新的历史最大值,由原来的历史最大值和当前点的值加上更新过程中最大的历史加标记二者取最优。

看题目条件,好家伙时间限制 \(40s\)。有个很烦的限制 \(a_i < x\) 的最大历史值。那么很明显的是一个区间内的历史最大值是很好都拿到的,每个数也是很好都拿到的,但是这里面不是所有数都有贡献,有啥办法可以快速找到需要判断的数?emmmm,有序好像就特别好做了。直接二分出最大的满足 \(a_j<x\)\(j\)。区间内要有序,然后 \(40s\) 时限的提醒。自然而然想到了根号级做法。分块的入门 P2801 教主的魔法。维护块内有序即可二分。

需要维护什么信息仔细想想。

1.首先每个块需要有 lazy 标记。非常简单:最基本的区间加标记和吉司机思想的历史最大加标记。
2.块内有序,是按照 \(a_i\) 进行排序,而我们显然在查询时可以知道贡献区间为 \(1 \sim j\),其中 \(j\) 是可以二分出来的最大的满足 \(a_j < x\)\(j\)。而我们需要知道这个前缀区间的最大历史值。很简单。维护块内有序的同时,并维护对应排序后的 \(a_i\) 对应位置的 \(h_{max}\) 的前缀最大值。

然后,然后就完了。剩下的就是卡常和常规的 DS 码工了。

考虑几个点的复杂度

1、对于重构一个块,根号重构。我们需要复制一份对应的 \((a_i,h_i)\),然后排序,然后再更新块内的 \(PrefixMax_{h_i}\)。这个复杂度显然是

\[O(2\times \sqrt{n}+\sqrt{n}\log{\sqrt{n}})=O(\sqrt{n}\log{\sqrt{n}}) \]

2、对于一次 pushDown 更新标记,我们就是正常一遍块内遍历更新。根据上述先后更新 \(h_{max}\)\(a_i\)。复杂度为 \(O(\sqrt{n})\)

3、二分出块内小等于<x的最大历史最值前缀max(区间max具有单调性)。这个复杂度显然为 \(O(\log{\sqrt{n}})\)

4、修改时,还是老规矩,单块内就暴力,多块就散块暴力,整块打标记更新。需要注意的是散块需要记得先 pushDown 以后再更新。然后优化下常数,其实可以开个 vis 数组,也可以当做标记的一种,记录哪些块发生了变化,需要在后续查询中重构。因为重构的复杂度是较高 的,所以我们也可以当做 lazy 去写。这里的复杂度显然是 \(O(\sqrt{n})\)

5、查询时,需要把该重构的重构一下。然后注意整块不要 pushDown,不然每个块都 pushDown 复杂度就错了,应该当做标记永久化的思想:查询时用 \(x-add\) 就行了,不需要 pushDown 以后的新 \(a_i\) 去算。然后就纯每个块二分。复杂度为 \(O(\sqrt{n}\log{\sqrt{n}})\)

6、建块时每个块需要 rebuild 一下,显然是 \(O(\sqrt{n} \times \sqrt{n}\log{\sqrt{n}})=O(n\log{\sqrt{n}})\),当然也可以全部记录为 \(vis\) 为 true 后更新。

参考代码
#include <bits/stdc++.h>

//#pragma GCC optimize("Ofast,unroll-loops")

#define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
typedef __int128 i128;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c)if (b & 1)(ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char))return;
    if (x < 0)x = -x, putchar('-');
    if (x > 9)write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow)return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3() { one = tow = three = 0; }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y)x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y)x = y;
}

constexpr int N = 5e5 + 10;
constexpr ll INF = 1e18 + 10;

int pos[N];
constexpr int SIZE = sqrt(N);
constexpr int CNT = (N + SIZE - 1) / SIZE;
int s[CNT + 1], e[CNT + 1];

ll a[N]; //当前序列
ll his[N]; //单点最大历史值
pll sortA[N]; //块内有序,并且附带对应的单点历史最大值
ll preHisMax[N]; //块内前缀历史最大值
bool vis[CNT + 1]; //需要重构块
struct Tag
{
    ll add; //加法标记
    ll hadd; //历史最大加
} tag[CNT + 1];

inline void rebuild(const int id)
{
    forn(i, s[id], e[id])sortA[i] = pll(a[i], his[i]);
    sort(sortA + s[id], sortA + e[id] + 1); //按当前序列排序
    //更新块内基于原序列为有序序列的前缀历史最大值
    preHisMax[s[id]] = sortA[s[id]].second;
    forn(i, s[id]+1, e[id])uMax(preHisMax[i] = sortA[i].second, preHisMax[i - 1]);
}

inline void push_down(const int id)
{
    if (!tag[id].add and !tag[id].hadd)return;
    forn(i, s[id], e[id])uMax(his[i], a[i] + tag[id].hadd), a[i] += tag[id].add; //更新当前序列并且更新当前历史最大值
    tag[id] = {0, 0}; //清空lazy
}

//二分出块内小等于<x的最大历史最值前缀max(区间max具有单调性)
inline ll binary(const int id, const ll x)
{
    int l = s[id], r = e[id];
    if (sortA[l].first >= x)return -INF;
    while (l < r)
    {
        if (const int mid = l + r + 1 >> 1; sortA[mid].first < x)l = mid;
        else r = mid - 1;
    }
    return max(preHisMax[l], sortA[l].first + tag[id].hadd);
}

inline ll query(const int l, const int r, const ll x)
{
    const int L = pos[l], R = pos[r];
    if (L == R)
    {
        if (vis[L])rebuild(L), vis[L] = false;
        ll ans = -INF;
        forn(i, l, r)if (a[i] + tag[L].add < x)uMax(ans, max(his[i], a[i] + tag[L].hadd));
        return ans;
    }
    if (vis[L])rebuild(L), vis[L] = false;
    if (vis[R])rebuild(R), vis[R] = false;
    ll ans = -INF;
    forn(i, l, e[L])if (a[i] + tag[L].add < x)uMax(ans, max(his[i], a[i] + tag[L].hadd));
    forn(i, s[R], r)if (a[i] + tag[R].add < x)uMax(ans, max(his[i], a[i] + tag[R].hadd));
    forn(i, L+1, R-1)
    {
        if (vis[i])rebuild(i), vis[i] = false;
        if (ll t = binary(i, x - tag[i].add); t != -INF)uMax(ans, t);
    }
    return ans;
}

inline void add(const int l, const int r, const ll x)
{
    const int L = pos[l], R = pos[r];
    if (L == R)
    {
        push_down(L);
        forn(i, l, r)uMax(his[i], a[i] += x);
        vis[L] = true;
        return;
    }
    push_down(L), push_down(R);
    forn(i, l, e[L])uMax(his[i], a[i] += x);
    forn(i, s[R], r)uMax(his[i], a[i] += x);
    vis[L] = vis[R] = true;
    forn(i, L+1, R-1)uMax(tag[i].hadd, tag[i].add += x);
}

int n, q;

inline void solve()
{
    cin >> n >> q;
    int siz = sqrt(n);
    int cnt = (n + siz - 1) / siz;
    forn(i, 1, n)cin >> a[i], his[i] = a[i], pos[i] = (i - 1) / siz + 1;
    forn(i, 1, cnt)s[i] = (i - 1) * siz + 1, e[i] = i * siz;
    e[cnt] = n;
    forn(i, 1, cnt)rebuild(i);
    forn(i, 1, q)
    {
        int op, l, r, val;
        cin >> op >> l >> r >> val;
        if (op == 1)add(l, r, val);
        else
        {
            if (ll ans = query(l, r, val); ans == -INF)cout << "-inf" << endl;
            else cout << ans << endl;
        }
    }
}

signed int main()
{
    Spider
    //------------------------------------------------------
    int test = 1;
    //    read(test);
    // cin >> test;
    forn(i, 1, test)solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
}