[ABC256Ex] I like Query Problem 题解

[ABC256Ex] I like Query Problem Solution

更好的阅读体验戳此进入

题面

给定 n,q,和序列 an,给定 q 次操作,有三种:

1 L R x:对于 [L,R] 内的所有 i 进行 aiaix

2 L R y:区间推平 [L,R]y

3 L R:输出 i=LRai

Solution

显然势能线段树,好像不是很难写。大概就是在一般的线段树基础上维护一个区间数是否均相同的标记,然后区间整除的时候直接通过这个实现 lazytag,这个复杂度经过一系列分析总之最后就是 O(nlog2n),实现起来很容易,代码也很短,不过,这太不优雅了

有区间推平操作,不难想到 ODT,显然会被卡,于是想到一种优化:

考虑这个的时候首先要知道一点语法知识,即对于 set 它是不同于一般的线性结构如 basic_string 的,一般的线性结构当插入删除时若改变 capacity 的话指针就会失效,但当在 set 中插入或删除元素的时候,对于非被删数元素的迭代器、指针和引用等都是不会失效的,用 cppreference 的话来说就是:

No iterators or references are invalidated.

众所周知,我们一般通过对变量的 mutable 修饰以使其可以在 set 中被修改,且按照 l 排序,所以这里我们可以将 r 也修饰为 mutable,这样的话我们便可以很方便的 O(1) 延申区间而不损失重构所需的 O(log),此时考虑,因为我们的 1 操作中 x2,所以显然对于一个数如果不推平为其它数的时候最多 log 次就会变为 0,所以我们在维护 1 操作时是可以同时将所有已经为 0 的数合并,这样可以有效减少块数,此时似乎对于 12 操作复杂度就已经正确了,不过如果我在每 log 次之后就重新推平满,这样复杂度似乎大概可能会被卡??不是很确定。

然后这样交上去会获得 28/30 的好成绩,不过依然会 T,也不难想到,我们直接拉满查询这个东西就退化成 O(nq) 的了。对于这个我最初的思路是,每当修改后再遇到一个 3 操作就重新建立一棵线段树维护所有数的值,这样在这次查询接着的所有查询就都是 O(logn) 的了,这样应该可以有效的水过一些构造的数据,当然卡的方法也很简单,只要在每次查询之间增加一次微小扰动的修改,但是这样每次都会重新建整棵树,最后复杂度还会退化为 12 常数的 O(nq)但是,经过下载数据点发现,T 掉的两个数据并没有如此设计,而是完全询问拉满,也就是说理论上这样实现之后是可以通过这道题的,估计出题人也没想到这种奇怪的做法。。。

但是我们可以尝试扩展这种做法,不难想到刚才说的做法复杂度主要浪费在了修改时只修改了一部分,而不需要整棵树重建,所以我们可以尝试在这里优化一下。不难想到,对于一般的线段树,其是支持除了 1 操作外所有操作的快速实现的,而 ODT 又可以将区间整除转化为区间推平,于是我们便不难想到,同时维护一棵 ODT 和一棵线段树,对于 1 操作在 ODT 上通过刚才说的优化转换为若干个区间推平,然后将推平作用到线段树上,对于 2 操作则 ODT 和线段树同时进行,对于 3 的查询直接在线段树上跑即可。这个的复杂度我没太想出来,不过翻了一下题解区,发现一篇 Blog [ABC256Ex] I like Query Problem 题解 和我的做法几乎相同,唯一的区别就是其在 ODT 维护区间整除的时候是直接重构的 ODT。总体来讲这两种维护方式差别是不大的,无非就是一只小 log 的区别,大佬的 Blog 最后分析的复杂度为 O(nlog2n),所以我的这个可能也是这个复杂度??总之表现还不错,最终 1.6s 跑完。

写在后面

这个奇怪的做法虽然能过,不过还是不推荐写,毕竟实现起来比较复杂细节较多,如果写的话似乎直接写重构部分 ODT 的方法会更好实现一些。一般的线段树写法实现大概也就 60 行,然后这个东西我实现大概写了将近两百行。。。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int N, Q;
int A[510000];

struct Node{
    int l;
    mutable int r;
    mutable ll val;
    friend const bool operator < (const Node &a, const Node &b){
        return a.l < b.l;
    }
};

class SegTree{
private:
public:
    ll tr[510000 << 2];
    ll lz[510000 << 2];
    #define LS (p << 1)
    #define RS (LS | 1)
    #define MID ((gl + gr) >> 1)
    #define SIZ(l, r) ((r) - (l) + 1)
public:
    SegTree(void){memset(lz, -1, sizeof lz);}
    void Pushup(int p){tr[p] = tr[LS] + tr[RS];}
    void Pushdown(int p, int gl, int gr){
        if(!~lz[p])return;
        lz[LS] = lz[RS] = lz[p];
        tr[LS] = SIZ(gl, MID) * lz[p];
        tr[RS] = SIZ(MID + 1, gr) * lz[p];
        lz[p] = -1;
    }
    void Build(int p = 1, int gl = 1, int gr = N){
        if(gl == gr)return tr[p] = A[gl = gr], void();
        Build(LS, gl, MID), Build(RS, MID + 1, gr);
        Pushup(p);
    }
    void Assign(int l, int r, ll v, int p = 1, int gl = 1, int gr = N){
        // printf("Assign ST : l = %d, r = %d, v = %lld, gl = %d, gr = %d, p = %d\n", l, r, v, gl, gr, p);
        if(l <= gl && gr <= r)return tr[p] = SIZ(gl, gr) * v, lz[p] = v, void();
        Pushdown(p, gl, gr);
        if(l <= MID)Assign(l, r, v, LS, gl, MID);
        if(MID + 1 <= r)Assign(l, r, v, RS, MID + 1, gr);
        Pushup(p);
    }
    ll Query(int l, int r, int p = 1, int gl = 1, int gr = N){
        if(l <= gl && gr <= r)return tr[p];
        if(r < gl || l > gr)return 0;
        Pushdown(p, gl, gr);
        return Query(l, r, LS, gl, MID) + Query(l, r, RS, MID + 1, gr);
    }
    void Desc(int siz = 3){
        int len(1);
        int cur(0);
        while(siz--){
            for(int i = 1; i <= len; ++i)printf("%lld%c", tr[++cur], i == len ? '\n' : ' ');
            len <<= 1;
        }
    }
}st;

class ODT{
private:
    set < Node > tr;
public:
    auto Insert(Node p){return tr.insert(p);}
    auto Split(int p){
        auto it = tr.lower_bound(Node{p});
        if(it != tr.end() && it->l == p)return it;
        advance(it, -1);
        if(p > it->r)return tr.end();
        int l = it->l, r = it->r;
        ll val = it->val;
        tr.erase(it);
        Insert(Node{l, p - 1, val});
        return Insert(Node{p, r, val}).first;
    }
    void Assign(int l, int r, ll val){
        auto itR = Split(r + 1), itL = Split(l);
        tr.erase(itL, itR);
        Insert(Node{l, r, val});
        st.Assign(l, r, val);
    }
    void Divide(int l, int r, ll x){
        auto itR = Split(r + 1), itL = Split(l);
        for(auto it = itL; it != itR;){
            it->val /= x;
            if(!it->val){
                decltype(it) nxt;
                for(nxt = next(it); nxt != itR; nxt = tr.erase(nxt)){
                    nxt->val /= x;
                    if(!nxt->val)it->r = nxt->r;
                    else{
                        st.Assign(it->l, it->r, it->val);
                        st.Assign(nxt->l, nxt->r, nxt->val);
                        it = next(nxt);
                        break;
                    }
                }
                if(nxt == itR)st.Assign(it->l, it->r, it->val), it = nxt;
            }else
                st.Assign(it->l, it->r, it->val), advance(it, 1);
        }
    }
    ll Query(int l, int r){
        ll ret(0);
        auto itR = Split(r + 1), itL = Split(l);
        for(auto it = itL; it != itR; ++it)
            ret += (it->r - it->l + 1) * it->val;
        return ret;
    }
    void Desc(void){
        printf("Describe ODT:\n");
        for(auto nod : tr)printf("%d ~ %d : %lld\n", nod.l, nod.r, nod.val);
    }
}odt;

int main(){
    // freopen("01_n_small_00.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    N = read(), Q = read();
    for(int i = 1; i <= N; ++i)odt.Insert(Node{i, i, A[i] = read()});
    st.Build();
    while(Q--){
        int opt = read();
        switch(opt){
            case 1:{
                int l = read(), r = read(), x = read();
                odt.Divide(l, r, x);
                break;
            }
            case 2:{
                int l = read(), r = read(), x = read();
                odt.Assign(l, r, x);
                break;
            }
            case 3:{
                int l = read(), r = read();
                printf("%lld\n", st.Query(l, r));
                break;
            }
            default: break;
        }
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

UPD

update-2022_12_08 初稿

update-2022_12_08 修复一点小锅

本文作者:tsawke

本文链接:https://www.cnblogs.com/tsawke/p/17032779.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Tsawke  阅读(26)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起