首页 > 其他分享 >P8659 [蓝桥杯 2017 国 A] 数组操作 题解

P8659 [蓝桥杯 2017 国 A] 数组操作 题解

时间:2024-01-25 16:22:35浏览次数:29  
标签:P8659 const val int 题解 void 蓝桥 curr define

题目链接:洛谷 或者 蓝桥杯 或者 C语言中文网

几个OJ的AC记录:

忘了哪个OJ的:

洛谷:

C语言中文网:

蓝桥杯:



emmmmmmm,好像每个OJ给的时限和空间还不一样,蓝桥杯官方还给了 $3s$ 和 $2G$, C语言中文网机子比较老可能,挺卡常的,开了个究极快读和指令集就过去了,也可以自己调下重构常数,偷懒了。

回到本题

说一下标算,一开始没注意没有区间覆盖操作,还想着 ODT 去写,其实正解应该是可持久化的文艺平衡树去想,就很简单了。首先这个操作二这玩意肯定不是线段树能搞得,总有 \([l1,r1]\) 和 \([l2,r2]\) 对应的线段树节点非对称的,不过线段树分裂或者合并倒是应该能写,但涉及到了分裂区间和合并区间我们显然选择了文艺平衡树去维护信息,具体的可以参考我前面提的一些相关题。文艺平衡树以下标为键值,同时维护子树大小与节点值,它的中序遍历则是原序列 \(a\),所以对于操作 \(1\) 和 操作 \(3\) 非常常规,我们文艺平衡树维护一个子树大小,在 \(split\) 的过程中,根据左子树大小来确定走哪边,和主席树那些是一样的。

再来说说操作二,操作二这玩意很显然,我们不可能把整段进行复制了然后再贴到另一段上去,但如果这两段共用一段呢?假如有个平衡树节点为 \([l2,r2]\) 范围的信息,这点两次 \(split\) 常规拿到,那么如何如果将 \([l1,r1]\) 的平衡树节点换成它是 ok 的。但有一个问题,假如我要修改 \([l1,r1]\) 的节点的信息,比如操作 \(1\),岂不是也会同时修改了 \([l2,r2]\) 的信息?很显然,这个时候我们需要可持久化,复制版本信息,这样虽然共用一个节点,但它们的版本不同,这样一来修改的情况就互不影响了。具体的,拿到两个节点以后,新的根节点应该为:\(merge([1,l1-1]、[l2,r2]、[r2+1,n])\) 这三个节点的 \(merge\),这点很常规,不熟悉的可以刷一下洛谷的可持久化文艺平衡树板题练习下。

细节

注意到空间限制 \(128mb\) 这玩意显然在提示我们需要重构,类似替罪羊树那种思想一样,重构树,只需要 \(dfs\) 一遍树以中序遍历的形式就能拿到原序列信息,因为我们的平衡树是文艺平衡树,以下标为值键的,即右子树都是处于当前数组位置右边的数,左子树则是左边的。而建树,有些人喜欢用直接常规的从中点往两边建树,但这样会破坏 FHQ 的小根堆或者大根堆的性质,虽然我们的这个 \(rnk\) 本质就是让它成链的可能性降低,即使破坏掉原有的性质,但为了保证 \(FHQ\) 的根堆的合理性,我还是使用了常规的笛卡尔树建树法,\(O(n)\) 建树,我们可以考虑,当如果节点数量大于某个阈值时,即树的节点数量太多时,我们可以重构树,因为有些节点已经没用了,来保证空间的大小,这里我选的是十倍树的大小。

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

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

#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 = 1e5 + 10;
int cnt;

struct FHQ
{
    int child[2], siz, rnk;
    ull add, sum, val;
    FHQ() = default;

    explicit FHQ(const ll val)
        : val(val)
    {
        sum = val;
        add = child[0] = child[1] = 0;
        siz = 1, rnk = Rand(INT_MIN,INT_MAX);
    }
} node[N * 20];

#define left(x) node[x].child[0]
#define right(x) node[x].child[1]
#define siz(x) node[x].siz
#define add(x) node[x].add
#define sum(x) node[x].sum
#define val(x) node[x].val
#define rnk(x) node[x].rnk

inline void push_up(const int curr)
{
    siz(curr) = siz(left(curr)) + siz(right(curr)) + 1;
    sum(curr) = sum(left(curr)) + sum(right(curr)) + val(curr);
}

//复制节点信息,开辟新版本节点
inline int NewNode(const int idx)
{
    node[++cnt] = node[idx];
    return cnt;
}

//加上一个树对一个树的信息影响,其实和线段树差不多的
inline void AddLazy(const int curr, const ll val)
{
    sum(curr) += siz(curr) * val;
    val(curr) += val;
    add(curr) += val;
}

//有下传标记开新节点信息维护
inline void push_down(const int curr)
{
    if (!add(curr))return;
    if (left(curr))
        left(curr) = NewNode(left(curr));
    if (right(curr))
        right(curr) = NewNode(right(curr));
    AddLazy(left(curr),add(curr));
    AddLazy(right(curr),add(curr));
    add(curr) = 0;
}

//分裂记得新版本信息维护
inline void split(const int curr, const int idx, int& x, int& y)
{
    if (!curr)
    {
        x = y = 0;
        return;
    }
    push_down(curr);
    if (siz(left(curr)) < idx)
    {
        x = NewNode(curr);
        split(right(curr), idx - siz(left(curr)) - 1,right(x), y);
        push_up(x);
    }
    else
    {
        y = NewNode(curr);
        split(left(curr), idx, x,left(y));
        push_up(y);
    }
}

//合并也记得新版本信息维护
inline int merge(const int x, const int y)
{
    if (!x or !y)return x ^ y;
    push_down(x), push_down(y);
    int curr;
    if (rnk(x) < rnk(y))
    {
        curr = NewNode(x);
        right(curr) = merge(right(curr), y);
    }
    else
    {
        curr = NewNode(y);
        left(curr) = merge(x,left(curr));
    }
    push_up(curr);
    return curr;
}

inline int merge(const int l, const int mid, const int r)
{
    return merge(merge(l, mid), r);
}

//两次分裂拿rson为[l,r]区间节点
inline void Add(int& curr, const int l, const int r, const ll val)
{
    int lTree, rTree;
    split(curr, r, lTree, rTree);
    int lson, rson;
    split(lTree, l - 1, lson, rson);
    AddLazy(rson, val);
    curr = merge(lson, rson, rTree);
}

inline void Copy(int& curr, const int l1, const int r1, const int l2, const int r2)
{
    int L11, R11, L12, R12; //对应区间:[1,r1]、[r1+1,n]、[1,l1-1]、[l1,r1]
    split(curr, r1, L11, R11);
    split(L11, l1 - 1, L12, R12);
    int L21, R21, L22, R22; //对应区间:[1,r2]、[r2+1,n]、[1,l2-1]、[l2,r2]
    split(curr, r2, L21, R21);
    split(L21, l2 - 1, L22, R22);
    curr = merge(L12, R22, R11); //由[1,l1-1]+[l2,r2]+[r1+1,n]这三个节点合并成新的根
}

//常规操作,同Add
inline ll Query(int& curr, const int l, const int r)
{
    int lTree, rTree;
    split(curr, r, lTree, rTree);
    int lson, rson;
    split(lTree, l - 1, lson, rson);
    ll ans = sum(rson);
    curr = merge(lson, rson, rTree);
    return ans;
}

int a[N];
int n, q;
int root;
int idx;
int version;
//中序遍历找新序列
inline void dfs(const int curr)
{
    if (!curr)return;
    push_down(curr);
    dfs(left(curr));
    a[++idx] = val(curr);
    dfs(right(curr));
}

//类似笛卡尔树单调栈建树
inline void build()
{
    cnt = 0;
    stack<int> st;
    forn(i, 1, n)
    {
        int last = 0;
        node[++cnt] = FHQ(a[i]);
        while (!st.empty() and rnk(st.top()) > rnk(cnt))push_up(last = st.top()), st.pop();
        if (!st.empty())
            right(st.top()) = cnt;
        left(cnt) = last, st.push(cnt);
    }
    while (!st.empty())
    {
        if (st.size() == 1)root = st.top();
        push_up(st.top()), st.pop();
    }
}

//重构
inline void rebuild()
{
    idx = 0;
    dfs(root);
    root = 0;
    build();
}

inline void solve()
{
    cin >> n >> q;
    forn(i, 1, n)cin >> a[i];
    build();
    forn(i, 1, q)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int l, r;
            ll val;
            cin >> l >> r >> val;
            Add(root, l, r, val);
        }
        else if (op == 2)
        {
            int l1, r1, l2, r2;
            cin >> l1 >> r1 >> l2 >> r2;
            Copy(root, l1, r1, l2, r2);
        }
        else
        {
            int l, r;
            cin >> l >> r;
            cout << Query(root, l, r) << endl;
        }
        if (cnt > 10 * n)rebuild(); //树节点太多就重构保证空间大小
    }
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    cin >> test;
    test = 1;
    forn(i, 1, test)solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[时间复杂度为:O(q\log{n}+C\times n),C为重构次数 \]

如果你被卡常了,可以考虑调整重构程度去优化,以及一些代码细节比如能用 \(int\) 的地方别用 \(long\ long\),需要再临时变,减少常数。不过亲测不咋卡常,正常写法能过。如果在C语言中文网被卡常的话,可以考虑快读或者指令集之类的一些优化手段。

标签:P8659,const,val,int,题解,void,蓝桥,curr,define
From: https://www.cnblogs.com/Athanasy/p/17987418

相关文章

  • P3355 骑士共存问题题解
    题目链接:P3355骑士共存问题-洛谷|计算机科学教育新生态(luogu.com.cn)题解:棋盘问题考虑黑白染色成为二分图后做。观察马的性质,可知一个点只能到一个异色点,所以,构造方案可以先将所有同色点放上马,再考虑有那些异色点不可以放置。方法一:网络流,时间复杂度为O(|E|min(|E|0.5......
  • P7900 [COCI2006-2007#2] SJECIŠTA_题解
    [COCI2006-2007#2]SJECIŠTA_题解rt我们来看一下题目描述考虑一个有$n$个顶点的凸多边形,且这个多边形没有任何三个(或以上)的对角线交于一点。这句话什么意思?当顶点为$n$的图形为正多边形时便有可能出现一个点是有三条线相交而构成的如图如图情况就有三个......
  • P9779_[HUSTFC 2023] 不定项选择题_题解
    rt题目有一道共n个选项的不定项选择题,它的答案至少包含一个选项,由于题目与选项的内容晦涩难懂,你打算通过尝试每一种可能的答案来通过这道题。初始时所有选项都没有被勾选,你可以执行任意次下述操作:勾选一个当前未被勾选的选项。取消勾选一个当前已被勾选的选项。当你......
  • P2045 方格取数加强版题解
    题目链接:P2045方格取数加强版-洛谷|计算机科学教育新生态(luogu.com.cn)题目:出一个n*n的矩阵,每一格有一个非负整数A{i,j}且A{i,j} <=10^3现在从(1,1)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该格子的数就变成0,这样一共走K次,现在要......
  • CF349B Color the Fence 题解 贪心
    贪心题意:你一共有\(v\)元,给你数字\(1\)~\(9\)的价值,求出你能够买下的数字组成的最大数。思路首先,我们知道能够买下的数字个数越多,组成数字的位数就越多,结果自然就越大,那么,根据贪心策略,我们可以先全买价格最便宜的数字(相同价格时,自然买更大的)。参考代码:intv;cin>>v;......
  • CF467C George and Job 题解 DP 前缀和
    DP前缀和题目链接题意:给你一个长度为\(n\)的序列,让你从这个序列中挑选出\(k\)个长度为\(m\)的区间,并且任意区间不相交。使得选出的数之和最大,求出这个数。解法:很经典的DP模型,我们定义\(f_{i,j}\)表示从前\(i\)个数选出了\(j\)个区间可以取得的最大值,那么答案为:\(f_{n,k}\)。......
  • SNOI 2024 题解(坑:D1T3 D2T1 D2T2)
    树V图相同\(f(i)\)的点必然构成一个连通块,不然一定无解。每一个连通块中需要选出一个关键点,考虑相邻连通块是否合法,发现条件其实很很好判,就是两个交界点的距离需要满足某个大小关系,容易预处理后\(O(1)\)判,于是\(f_{u,x}\)表示\(u\)连通块内取\(x\)的方案数,DP即可。......
  • 关于php进行post出现500的超时问题解决办法
      最近搞个项目使用php进行post请求,时间长了就会出现500错误,ngnix报了个错误:upstreamtimedout(10060:Aconnectionattemptfailedbecausetheconnectedpartydidnotproperlyrespondafteraperiodoftime,orestablishedconnectionfailedbecauseconnected......
  • P8664 [蓝桥杯 2018 省 A] 付账问题
    贪心,把钱最多的放在后面兜底,前面的能付多少付多少#include<iostream>#include<stdio.h>#include<algorithm>#include<string>#include<math.h>#defineFor(i,j,n)for(inti=j;i<=n;++i)usingnamespacestd;constintN=5e5+5;int......
  • P1481魔族密码 题解(字典树)
    魔族密码题目背景风之子刚走进他的考场,就……花花:当当当当~~偶是魅力女皇——花花!!^^(华丽出场,礼炮,鲜花)风之子:我呕……(杀死人的眼神)快说题目!否则……-_-###题目描述花花:……咦好冷我们现在要解决的是魔族的密码问题(自我陶醉:搞不好魔族里面还会有人用密码给我和菜虫写情书咧,哦......