alg2025/src/10/4/P3391.cpp
Zengtudor e70c668e57 feat: 添加5911.cpp和P3391.cpp两个算法实现文件
5911.cpp实现了一个动态规划解决方案,用于解决特定条件下的最优组合问题
P3391.cpp实现了一个基于Treap数据结构的区间反转操作,支持高效区间操作
2025-10-04 20:48:04 +08:00

185 lines
4.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
// 最大节点数N + 1 (留出下标0作为空节点)
const int MAXN = 200005;
// 节点结构体
struct Node {
int l, r; // 左右子节点的数组下标 (代替指针)
int val; // 节点存储的值 (本题中为 1 到 N)
int size; // 以该节点为根的子树大小
int rnd; // 随机优先级 (维护堆性质)
bool rev; // 反转延迟标记 (Lazy Tag)
} tree[MAXN];
int root = 0; // Treap 的根节点下标
int idx = 0; // 节点分配计数器
// --- 辅助函数 ---
// 1. 获取子树大小
int get_size(int p) {
return p ? tree[p].size : 0;
}
// 2. 信息更新 (更新子树大小)
void push_up(int p) {
if (!p) return;
tree[p].size = get_size(tree[p].l) + get_size(tree[p].r) + 1;
}
// 3. 标记下传 (Pushdown)
// 当需要访问或修改子节点时,必须先将父节点的标记下传
void push_down(int p) {
if (!p || !tree[p].rev) return;
// 交换左右子树 (结构上的反转)
swap(tree[p].l, tree[p].r);
// 向子节点传递反转标记
if (tree[p].l) tree[tree[p].l].rev ^= 1;
if (tree[p].r) tree[tree[p].r].rev ^= 1;
// 清除当前节点的标记
tree[p].rev = false;
}
// 4. 节点创建
int new_node(int v) {
idx++;
tree[idx].val = v;
tree[idx].size = 1;
tree[idx].rnd = rand(); // C++ rand() 简单实现,实际竞赛需更优随机数
tree[idx].l = tree[idx].r = 0;
tree[idx].rev = false;
return idx;
}
// --- 核心操作 ---
// 5. 合并 (Merge) 操作
// 将 t1 和 t2 合并,要求 t1 中所有元素在中序遍历上都在 t2 之前
// 返回合并后的新树根下标
int merge(int t1, int t2) {
if (!t1 || !t2) return t1 + t2; // 如果其中一个为空,返回另一个
// 合并前,先下传标记
push_down(t1);
push_down(t2);
// 比较优先级 (rnd),优先级高的作为新根
if (tree[t1].rnd > tree[t2].rnd) {
// t1 作为根t1的右子树与 t2 合并
tree[t1].r = merge(tree[t1].r, t2);
push_up(t1); // 更新 t1 的 size
return t1;
} else {
// t2 作为根t1 与 t2 的左子树合并
tree[t2].l = merge(t1, tree[t2].l);
push_up(t2); // 更新 t2 的 size
return t2;
}
}
// 6. 分裂 (Split) 操作 (按大小 k 分裂)
// 将 p 分裂成 t1 (前 k 个元素) 和 t2 (剩余元素)
// 注意t1 和 t2 是通过引用传递的,会直接修改外部变量
void split(int p, int k, int &t1, int &t2) {
if (!p) {
t1 = t2 = 0;
return;
}
// 分裂前,先下传标记
push_down(p);
int s_l = get_size(tree[p].l); // 左子树的大小
if (k <= s_l) {
// 当前节点 p 及其右子树都属于 t2t1 只在 p 的左子树中
split(tree[p].l, k, t1, tree[p].l); // 递归分裂左子树
t2 = p; // p 成为 t2 的根 (已连接其左子树为 tree[p].l)
} else {
// 当前节点 p 及其左子树都属于 t1t2 只在 p 的右子树中
// k' = k - s_l - 1 是需要在右子树中寻找的元素个数
split(tree[p].r, k - s_l - 1, tree[p].r, t2); // 递归分裂右子树
t1 = p; // p 成为 t1 的根 (已连接其右子树为 tree[p].r)
}
push_up(p); // 更新 p 的 size
}
// --- 区间反转操作 ---
// 7. 区间反转 [l, r]
void reverse_interval(int l, int r) {
int t1, t2, t3;
// (1) 分裂出左侧部分 [1, l-1] -> t1
// t1: [1, l-1] | t2: [l, N]
split(root, l - 1, t1, t2);
// (2) 分裂出中间部分 [l, r] -> t2
// t2: [l, r] | t3: [r+1, N]
split(t2, r - l + 1, t2, t3);
// (3) 对中间部分打上反转标记
if (t2) {
tree[t2].rev ^= 1;
}
// (4) 合并回去
// root = t1 | (t2 | t3)
root = merge(t1, merge(t2, t3));
}
// --- 8. 中序遍历输出结果 ---
void print_inorder(int p) {
if (!p) return;
// 访问前,先下传标记,确保结构正确
push_down(p);
print_inorder(tree[p].l);
cout << tree[p].val << " ";
print_inorder(tree[p].r);
}
// --- 主函数 ---
int main() {
// 设置随机数种子
srand(time(0));
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m;
cin >> n >> m;
// 初始化序列: 1, 2, 3, ..., N
for (int i = 1; i <= n; ++i) {
// 依次将新节点合并到 Treap 的最右端
root = merge(root, new_node(i));
}
// 执行 M 次操作
for (int i = 0; i < m; ++i) {
int l, r;
cin >> l >> r;
reverse_interval(l, r);
}
// 输出最终序列
print_inorder(root);
cout << endl;
return 0;
}