P2042 [NOI2005] 维护数列 题解

news/2025/9/26 13:51:50/文章来源:https://www.cnblogs.com/MoyouSayuki/p/19111028

P2042 [NOI2005] 维护数列 题解

平衡树

因为操作里面有翻转,严格强于文艺平衡树,所以考虑平衡树维护数列。

  1. 直接暴力插入即可
  2. 分裂出删除的子段,然后合并其两端的平衡树
  3. 分裂出修改的子段,然后打推平的懒标记,
  4. 分裂出翻转的子段,交换根的左右子树,然后打翻转的懒标记
  5. 维护子树和
  6. 维护子树最大子段和,本题子段不可为空。

因为要维护最大子段和,在翻转的时候需要交换左边开始最大和与右边开始最大和,这里我们维护左右最大和的时候可以为空,因为平衡树上传信息的时候包含当前节点(非 leafy tree),所以自然不会得到空子段。

注意到本题空间限制只有 128Mb,需要回收删除的节点减少空间消耗。

时间复杂度:\(O(N\log N)\)

// Problem: P2042 [NOI2005] 维护数列
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2025 Moyou All rights reserved.
// Date: 2025-09-02 22:52:54#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <random>
#include <ctime>
// #define int long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 5e5 + 10, M = 4e6 + 10, INF = 1e9;int n, m, tag[N], rvs[N], sum[N], dat[N], lm[N], rm[N], ls[N], rs[N], siz[N], val[N], rnd[N], rt, idx, trash[M], tott;
mt19937 rando(time(0));
int New(int u) {int p = trash[tott];if(!tott) p = ++ idx;else tott --;// int p = ++ idx;tag[p] = -INF, ls[p] = rs[p] = rvs[p] = 0;val[p] = u, sum[p] = u, dat[p] = u, lm[p] = rm[p] = max(0, u);rnd[p] = rando(), siz[p] = 1;return p;
} 
void up(int u) {siz[u] = siz[ls[u]] + siz[rs[u]] + 1;sum[u] = sum[ls[u]] + sum[rs[u]] + val[u];dat[u] = max({dat[ls[u]], dat[rs[u]], rm[ls[u]] + lm[rs[u]] + val[u]});lm[u] = max(lm[ls[u]], sum[ls[u]] + val[u] + lm[rs[u]]);rm[u] = max(rm[rs[u]], sum[rs[u]] + val[u] + rm[ls[u]]);
}
void align(int u, int v) {if(!u) return ;sum[u] = siz[u] * v, dat[u] = max(v, sum[u]), lm[u] = rm[u] = max(0, sum[u]), val[u] = v, tag[u] = v;
}
void Reverse(int u) {if(!u) return ;swap(ls[u], rs[u]), swap(lm[u], rm[u]), rvs[u] ^= 1;
}
void down(int u) {if(rvs[u]) Reverse(ls[u]), Reverse(rs[u]), rvs[u] = 0;if(tag[u] != -INF) align(ls[u], tag[u]), align(rs[u], tag[u]), tag[u] = -INF;
}
int merge(int a, int b) {if(!a || !b) return a + b;down(a), down(b);if(rnd[a] < rnd[b]) return rs[a] = merge(rs[a], b), up(a), a;return ls[b] = merge(a, ls[b]), up(b), b;
}
void split(int p, int v, int &a, int &b) {if(!p) return a = b = 0, void();down(p);if(siz[ls[p]] + 1 <= v) a = p, split(rs[p], v - siz[ls[p]] - 1, rs[p], b);else b = p, split(ls[p], v, a, ls[p]);up(p);
}
void reuse(int u) {if(!u) return ;trash[++ tott] = u;reuse(ls[u]), reuse(rs[u]);
}signed main() {ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;for(int i = 1, x; i <= n; i ++) cin >> x, rt = merge(rt, New(x));dat[0] = -INF;for(int i = 1, pos, tot, a, b, c, v; i <= m; i ++) {string s; cin >> s;if(s[0] == 'I') {cin >> pos >> tot;split(rt, pos, a, b);for(int i = 1, x; i <= tot; i ++) cin >> x, a = merge(a, New(x));rt = merge(a, b);}else if(s[0] == 'D') {cin >> pos >> tot;split(rt, pos - 1, a, b);split(b, tot, b, c);reuse(b);rt = merge(a, c);}else if(s[0] == 'M' && s.back() == 'E') {cin >> pos >> tot >> v;split(rt, pos - 1, a, b);split(b, tot, b, c);align(b, v);rt = merge(merge(a, b), c);}else if(s[0] == 'R') {cin >> pos >> tot;split(rt, pos - 1, a, b);split(b, tot, b, c);Reverse(b);rt = merge(merge(a, b), c);}else if(s[0] == 'G') {cin >> pos >> tot;split(rt, pos - 1, a, b);split(b, tot, b, c);cout << sum[b] << '\n';rt = merge(merge(a, b), c);}else {cout << dat[rt] << '\n';}}return 0;
}

DeepSeek 修改后我的代码:

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <random>
#include <ctime>
using namespace std;typedef pair<int, int> PII;
typedef long long ll;
const int N = 5e5 + 10, M = 4e6 + 10, INF = 1e9;// 平衡树节点结构
struct Node {int left, right;    // 左右子节点int value;          // 节点值int size;           // 子树大小int priority;       // 随机优先级(用于Treap)int sum;            // 子树区间和int max_sum;        // 子树最大子段和int prefix_max;     // 最大前缀和int suffix_max;     // 最大后缀和int lazy_assign;    // 区间赋值懒标记bool lazy_reverse;  // 区间反转懒标记
};Node tree[M];
int root, node_count;
int recycled_nodes[M], recycle_count;
mt19937 rng(time(0));// 创建一个新节点
int create_node(int value) {int id;if (recycle_count > 0) {id = recycled_nodes[recycle_count--];} else {id = ++node_count;}tree[id].left = tree[id].right = 0;tree[id].value = value;tree[id].size = 1;tree[id].priority = rng();tree[id].sum = value;tree[id].max_sum = value;tree[id].prefix_max = max(0, value);tree[id].suffix_max = max(0, value);tree[id].lazy_assign = -INF;tree[id].lazy_reverse = false;return id;
}// 更新节点信息
void update(int id) {if (id == 0) return;Node &node = tree[id];Node &left = tree[node.left];Node &right = tree[node.right];node.size = left.size + right.size + 1;node.sum = left.sum + right.sum + node.value;// 计算最大子段和node.max_sum = max({left.max_sum, right.max_sum, left.suffix_max + node.value + right.prefix_max});// 计算最大前缀和node.prefix_max = max(left.prefix_max, left.sum + node.value + right.prefix_max);// 计算最大后缀和node.suffix_max = max(right.suffix_max, right.sum + node.value + left.suffix_max);
}// 应用区间赋值标记
void apply_assign(int id, int value) {if (id == 0) return;Node &node = tree[id];node.value = value;node.sum = value * node.size;node.max_sum = max(value, node.sum);node.prefix_max = node.suffix_max = max(0, node.sum);node.lazy_assign = value;
}// 应用区间反转标记
void apply_reverse(int id) {if (id == 0) return;Node &node = tree[id];swap(node.left, node.right);swap(node.prefix_max, node.suffix_max);node.lazy_reverse = !node.lazy_reverse;
}// 下传标记
void push_down(int id) {if (id == 0) return;Node &node = tree[id];if (node.lazy_reverse) {apply_reverse(node.left);apply_reverse(node.right);node.lazy_reverse = false;}if (node.lazy_assign != -INF) {apply_assign(node.left, node.lazy_assign);apply_assign(node.right, node.lazy_assign);node.lazy_assign = -INF;}
}// 合并两棵树
int merge(int left, int right) {if (left == 0 || right == 0) return left + right;push_down(left);push_down(right);if (tree[left].priority < tree[right].priority) {tree[left].right = merge(tree[left].right, right);update(left);return left;} else {tree[right].left = merge(left, tree[right].left);update(right);return right;}
}// 分裂树
void split(int id, int size, int &left, int &right) {if (id == 0) {left = right = 0;return;}push_down(id);int left_size = tree[tree[id].left].size;if (left_size + 1 <= size) {left = id;split(tree[id].right, size - left_size - 1, tree[id].right, right);} else {right = id;split(tree[id].left, size, left, tree[id].left);}update(id);
}// 回收节点
void recycle_tree(int id) {if (id == 0) return;recycled_nodes[++recycle_count] = id;recycle_tree(tree[id].left);recycle_tree(tree[id].right);
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int n, m;cin >> n >> m;tree[0].max_sum = -1e9;// 初始化树root = 0;for (int i = 0; i < n; i++) {int value;cin >> value;root = merge(root, create_node(value));}// 处理操作for (int i = 0; i < m; i++) {string op;cin >> op;if (op == "INSERT") {int pos, count;cin >> pos >> count;int left, right;split(root, pos, left, right);for (int j = 0; j < count; j++) {int value;cin >> value;left = merge(left, create_node(value));}root = merge(left, right);} else if (op == "DELETE") {int pos, count;cin >> pos >> count;int left, mid, right;split(root, pos - 1, left, mid);split(mid, count, mid, right);recycle_tree(mid);root = merge(left, right);} else if (op == "MAKE-SAME") {int pos, count, value;cin >> pos >> count >> value;int left, mid, right;split(root, pos - 1, left, mid);split(mid, count, mid, right);apply_assign(mid, value);root = merge(merge(left, mid), right);} else if (op == "REVERSE") {int pos, count;cin >> pos >> count;int left, mid, right;split(root, pos - 1, left, mid);split(mid, count, mid, right);apply_reverse(mid);root = merge(merge(left, mid), right);} else if (op == "GET-SUM") {int pos, count;cin >> pos >> count;int left, mid, right;split(root, pos - 1, left, mid);split(mid, count, mid, right);cout << tree[mid].sum << '\n';root = merge(merge(left, mid), right);} else if (op == "MAX-SUM") {cout << tree[root].max_sum << '\n';}}return 0;
}

块状链表

考虑分块,结合链表和数组的优点,维护一个链表,链表里面每一项都是一个数组,维护的时候钦定数组大小不超过 \(2B\),否则分裂这个数组,如果相邻两个数组大小加起来不超过 \(B\),那么把他们合并起来,这样每个数组大小都差不多在 \(B\) 左右,类似一个动态的分块,显然块状链表有 \(O(\dfrac nB)\) 项。

块状链表的一个重要函数是 split(pos),表示把块状链表里第 \(pos\) 个数字所在数组分裂,得到的新数组编号。

  1. 插入,split(pos),然后在新数组前插入,时间复杂度 \(O(B + \dfrac nB)\)

  2. 删除,L = split(pos - 1), R = split(pos + tot - 1),然后用链表的手段删除 \([L, R)\) 内的数组,也就是让 \(prev_L\)next 指针指向 \(R\),当然如果 \(L\) 是头指针,直接让头指针变成 \(R\)

  3. 修改,还是同上,先把区间分裂出来,然后区间里面每个数组都打推平的懒标记。

  4. 翻转,先把区间分裂出来,然后注意到翻转这个区间等价于先翻转链表上数组的顺序,再翻转每个数组内元素,所以先翻转这个区间里面的数组顺序,然后给每个数组打翻转的懒标记。

  5. 求和,维护数组内和。

  6. 最大子段和,维护同平衡树做法。

Tips:

  1. 给定的数组不要忘记初始化
  2. 注意插入的时候是在数组前面插入还是后面插入
  3. tot可能为0,需要特判
  4. 删除区间的时候如果把头指针删掉了,要确定新的头指针
  5. 如果不合并相邻小数组复杂度是错的
// Problem: P2042 [NOI2005] ά»¤ÊýÁÐ
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2025 Moyou All rights reserved.
// Date: 2025-09-02 22:52:54#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <random>
#include <ctime>
// #define int long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 5e5 + 10, M = 4e6 + 10, B = 300, INF = 1e9;int n, m, rvs[N], tag[N], ne[N], hd, lm[N], rm[N], sum[N], dat[N], idx;
vector<int> a[N];
void recalc(int u) {if(tag[u] != -INF) {for(auto &x : a[u]) x = tag[u];tag[u] = -INF, rvs[u] = 0;}if(rvs[u]) reverse(a[u].begin(), a[u].end()), rvs[u] = 0;lm[u] = rm[u] = sum[u] = dat[u] = -INF;if(a[u].size()) {int s = 0;for(int i = 0; i < a[u].size(); i ++)s += a[u][i], lm[u] = max(lm[u], s);// cout << "RE " << u << ' ' << a[u].size() << '\n';s = 0;for(int i = a[u].size() - 1; ~i; i --)s += a[u][i], rm[u] = max(rm[u], s);sum[u] = s;int dp = -INF;for(auto o : a[u]) {dp = max(dp + o, o);dat[u] = max(dat[u], dp);}}
}
int New(vector<int> v) {int p = ++ idx; a[p] = v, rvs[p] = 0, tag[p] = -INF, ne[p] = 0;recalc(p);return p;
}
void check(int x) {recalc(x);while(a[x].size() > B * 2) {vector<int> t;while(t.size() < B)t.push_back(a[x].back()), a[x].pop_back();reverse(t.begin(), t.end());int p = New(t);ne[p] = ne[x], ne[x] = p;}recalc(x);
}
PII split(int pos) {if(!pos) {int p = New({});ne[p] = hd, hd = p;return {0, p};}int i;for(i = hd; i; i = ne[i]) {if(pos <= a[i].size()) {check(i);}if(pos < a[i].size()) {vector<int> t;while(a[i].size() > pos) t.push_back(a[i].back()), a[i].pop_back();reverse(t.begin(), t.end());int p = New(t);ne[p] = ne[i], ne[i] = p;recalc(i);return {i, p};}else if(pos == a[i].size()) {int p = New({});ne[p] = ne[i], ne[i] = p;recalc(i);return {i, p};}pos -= a[i].size();}cout << "INF  \n";return {-1, -1};
}signed main() {ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m, idx = 0;vector<int> t;for(int i = 1, x; i <= n; i ++) cin >> x, t.push_back(x);hd = New(t);check(hd);dat[0] = -INF;for(int i = 1, pos, tot, b, c, v; i <= m; i ++) {string s; cin >> s;if(s[0] == 'I') {cin >> pos >> tot;int L = split(pos).y;vector<int> t;for(int i = 1, x; i <= tot; i ++) cin >> x, t.push_back(x);for(int o : a[L]) t.push_back(o);a[L] = t;recalc(L);}else if(s[0] == 'D') {cin >> pos >> tot;if(!tot) continue;int L = split(pos - 1).x, R = split(pos + tot - 1).y;for(int i = ne[L]; i != R && i; i = ne[i]) {a[i] = vector<int>();}if(L) ne[L] = R;else hd = R;}else if(s[0] == 'M' && s.back() == 'E') {cin >> pos >> tot >> v;if(!tot) continue;int L = split(pos - 1).y, R = split(pos + tot - 1).x;for(int i = L; ; i = ne[i]) {tag[i] = v, rvs[i] = 0, sum[i] = a[i].size() * v;lm[i] = rm[i] = dat[i] = max(v, sum[i]);if(i == R) break;}}else if(s[0] == 'R') {cin >> pos >> tot;if(!tot) continue;auto tmp = split(pos - 1);int pl = tmp.x, L = tmp.y;tmp = split(pos + tot - 1);int R = tmp.x, nr = tmp.y;vector<int> t;for(int i = L; ; i = ne[i]) {rvs[i] ^= 1;t.push_back(i);swap(lm[i], rm[i]);if(i == R) break;}reverse(t.begin(), t.end());t.push_back(nr);if(pl) ne[pl] = R;else hd = R;for(int i = 0; i < (int)t.size() - 1; i ++)ne[t[i]] = t[i + 1];}else if(s[0] == 'G') {cin >> pos >> tot;if(!tot) {cout << 0 << '\n';continue;}int s = 0, L = split(pos - 1).y, R = split(pos + tot - 1).x;for(int i = L; ; i = ne[i]) {if(a[i].empty()) continue;s += sum[i];if(i == R) break;}cout << s << '\n';}else {int lm_ = -INF, rm_ = -INF, dat_ = -INF, sum_ = 0;for(int i = hd; i; i = ne[i]) {if(a[i].empty()) continue;dat_ = max({dat_, dat[i], rm_ + lm[i]});lm_ = max(lm_, sum_ + lm[i]);rm_ = max(rm[i], rm_ + sum[i]);sum_ += sum[i];}cout << dat_ << '\n';}while(a[hd].empty()) hd = ne[hd];for(int i = hd; i; i = ne[i]) {while(ne[i] && a[ne[i]].size() + a[i].size() <= B) {recalc(i), recalc(ne[i]);for(auto o : a[ne[i]]) a[i].push_back(o);a[ne[i]] = vector<int>();recalc(i);ne[i] = ne[ne[i]];}}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/917019.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

达梦数据库查询字段类型为Date 修改为DateTime

SELECT ALTER TABLE || OWNER || . || TABLE_NAME || MODIFY || COLUMN_NAME || DATETIME; AS alter_sql FROM ALL_TAB_COLUMNS WHERE DATA_TYPE = DATE and OWNER=PS_EXAMPLEDBUSER order by COLUMN_NAME asc

详细介绍:PyTorch 神经网络工具箱

详细介绍:PyTorch 神经网络工具箱pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&…

C++ new 操作符在操作系统层执行了什么操作?

C++ new 操作符在操作系统层执行了什么操作?在C++中,new操作符的执行涉及操作系统层面的内存分配和对象构造过程,具体可分为以下几个关键步骤: 1. 调用内存分配函数(operator new) new操作符首先通过operator ne…

[ABC422F-G] 题解

QwQ[ABC422F-G] 题解 F - Eat and Ride 考虑 DP,DP 状态要么压和要么压长度,如果压和就很直接,但是显然复杂度会爆炸,如果压长度的话,可以发现每到一个新点都要算:这条路径中在它后面的点的个数乘上它的点权,所…

天津模板建站代理wordpress增加赞赏

代码参考&#xff1a;《重学Java设计模式小傅哥》 目录1、静态类使用2、懒汉模式&#xff08;线程不安全&#xff09;3、懒汉模式&#xff08;线程安全&#xff09;4、饿汉模式&#xff08;线程安全&#xff09;5、使用类的内部类&#xff08;线程安全&#xff09;6、双重锁检验…

最新获取网站访客qq接口推客平台有哪些

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体邀约的好处主要体现在提高品牌知名度、扩大受众群体以及与媒体建立良好的合作关系。 媒体邀约是一种有效的公关策略&#xff0c;通过吸引媒体关注来促进信息的传播。它可以帮助组织…

山东省住房和城乡建设部网站首页四川润邦建设工程设计有限公司网站

大家好&#xff0c;我是阿赵。   这篇文章我想写了很久&#xff0c;是关于Unity项目使用AssetBundle加载资源时的内存管理的。这篇文章不会分享代码&#xff0c;只是分享思路&#xff0c;思路不一定正确&#xff0c;欢迎讨论。   对于Unity引擎的资源内存管理&#xff0c;我…

公司网站模板源代码常州微网站建设文档

如果有客户端1、客户端2等N个客户端争抢一个 Zookeeper 分布式锁。大致如下&#xff1a; 1&#xff1a; 大家都是上来直接创建一个锁节点下的一个接一个的临时有序节点 2&#xff1a; 如果自己不是第一个节点&#xff0c;就对自己上一个节点加监听器 3&#xff1a; 只要上一…

c# Listdynamic 按字段排序

public static List<dynamic> OrderByKey (this IList<dynamic> list, string propertyName, bool isDescending = false){var propertyInfo = list[0].GetType().GetProperty(propertyName);if (isDescen…

你看到的和你想要的

你看到的和你想要的 漫思

建设一个货代网站想要多少钱做谷歌网站使用什么统计代码

官方文档&#xff1a;入门指南 | Selenium Selenium是一个用于Web应用测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。 所以使用这个前端测试话工具&#xff0c;可以自动化做很多事情&#xff0c;比如自动化抓取网页内容&#xff0c;俗称网…

大兴网站定制开发房地产招新人的坑

依赖倒转原则 在大话设计模式这本书中&#xff0c;作者通过电话修电脑这个例子引入了面向对象设计的基本原则之一&#xff1a;依赖倒转原则。 概念 依赖倒转原则是面向对象设计的基本原则之一&#xff0c;它用于减少类之间的耦合&#xff0c;提高系统的灵活性和可维护性。在…

东莞网站制作电话糗事百科 wordpress

文章目录1. 题目2. 解题1. 题目 给定一个字符串 s&#xff0c;将 s 分割成一些子串&#xff0c;使每个子串都是回文串。 返回符合要求的最少分割次数。 示例: 输入: "aab" 输出: 1 解释: 进行一次分割就可将 s 分割成 ["aa","b"] 这样两个回文…

济南网站建设公司 推荐行知科技自己做网站用什么app

前言 本文主要介绍Redis的三种持久化方式、AOF持久化策略等 什么是持久化 持久化是指将数据在内存中的状态保存到非易失性介质&#xff08;如硬盘、固态硬盘等&#xff09;上的过程。在计算机中&#xff0c;内存中的数据属于易失性数据&#xff0c;一旦断电或重启系统&#…

别再靠 “关设备” 减碳!EMS 的 “预测性控能”,让企业满产也能达标双碳

在 “双碳” 目标推进的当下,减碳已成为企业发展的必答题。然而,不少企业仍陷入 “减碳就减产” 的困境 —— 为了降低碳排放,不得不采取关停生产线、限制设备运行时长等简单粗暴的方式,结果导致订单交付延迟、产能…

双活、异地多活架构怎么设计才不翻车? - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

阿里云客服界面

阿里云客服界面 漫思

关于认证系统安全的产品能力补齐

关于认证系统安全的产品能力补齐 漫思

网站个人简介怎么做公司建一个网站多少钱

transition: box-shadow 0.3s; 给按钮加效果 transition: all 0.7s; 给进度条