数据结构
基础数据结构
1. 链表
单链表
int e[N], ne[N];
int h = -1, idx = 0;
void insert(int a) // 在链表头插入一个数a
{
e[idx] = a, ne[idx] = h, h = idx++;
}
void remove() // 将头结点删除,需要保证头结点存在
{
h = ne[h];
}
双链表
int e[N], l[N], r[N], idx;
void init()
{
r[0] = 1, l[1] = 0; // 0是左端点,1是右端点
idx = 2;
}
void insert(int a, int x) // 在节点a的右边插入一个数x
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx++;
}
void remove(int a) // 删除节点a
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
例题
P1160 队列安排 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream>
using namespace std;
const int N = 100001;
class node
{
public:
int l, r, e;
node(int _e = 0, int _l = 0, int _r = 0)
{
l = _l;
r = _r;
e = _e;
}
};
node s[N];
int n, m, tot, idx[N];
void insert_r(int x, int y)
{
int now = idx[x];
s[++tot] = node(y, now, s[now].r);
s[s[now].r].l = tot;
s[now].r = tot;
idx[y] = tot;
}
void insert_l(int x, int y)
{
int now = idx[x];
s[++tot] = node(y, s[now].l, now);
s[s[now].l].r = tot;
s[now].l = tot;
idx[y] = tot;
}
void del(int x)
{
int now = idx[x];
int le = s[now].l, rt = s[now].r;
s[le].r = rt;
s[rt].l = le;
idx[x] = 0;
}
int main()
{
s[0] = node();
int x, k, p, now;
cin >> n;
insert_r(0, 1);
for (int i = 2; i <= n; i++)
{
cin >> k >> p;
p ? insert_r(k, i) : insert_l(k, i);
}
cin >> m;
for (int i = 1; i <= m; i++)
{
cin >> x;
if (idx[x])
del(x);
}
now = s[0].r;
while (now)
{
cout << s[now].e << ' ';
now = s[now].r;
}
return 0;
}
2. 栈
后进先出
\(n\) 个不同的元素进栈,出栈元素不同排列的个数为 \(\frac{1}{n+1}\) \(C_{2n}^n\),上述公式成为卡特兰 (Catalan) 数
手写栈
int stk[N], tt = 0; // tt表示栈顶
stk[++tt] = x; // 向栈顶插入一个数
tt--; // 从栈顶弹出一个数
stk[tt]; // 栈顶的值
if (tt > 0) // 判断栈是否为空,如果 tt > 0,则表示不为空
{
}
单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
3. 队列
手写队列
普通队列
int q[N], hh = 0, tt = -1;
q[++tt] = x; // 向队尾插入一个数
hh++; // 从队头弹出一个数
q[hh]; // 队头的值
if (hh <= tt) // 判断队列是否为空,如果 hh <= tt,则表示不为空
{
}
循环队列
int q[N], hh = 0, tt = 0;
// 向队尾插入一个数
q[tt++] = x;
if (tt == N)
tt = 0;
// 从队头弹出一个数
hh++;
if (hh == N)
hh = 0;
q[hh]; // 队头的值
if (hh != tt) // 判断队列是否为空,如果hh != tt,则表示不为空
{
}
单调队列
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
int n, m;
cin >> n >> m;
vector<int> a(n + 1);
deque<int> q;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
{
while (!q.empty() && a[q.back()] > a[i])
q.pop_back();
q.push_back(i);
if (i >= m)
{
while (!q.empty() && q.front() <= i - m)
q.pop_front();
cout << a[q.front()] << ' ';
}
}
cout << endl;
q.clear();
for (int i = 1; i <= n; i++)
{
while (!q.empty() && a[q.back()] < a[i])
q.pop_back();
q.push_back(i);
if (i >= m)
{
while (!q.empty() && q.front() <= i - m)
q.pop_front();
cout << a[q.front()] << ' ';
}
}
cout << endl;
双端队列
优先队列
约瑟夫问题
P1996 约瑟夫问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
void solve()
{
int n, m;
cin >> n >> m;
queue<int> q;
for (int i = 1; i <= n;i++)
q.push(i);
while(q.size()!=1)
{
for (int i = 1; i < m;i++)
{
q.push(q.front());
q.pop();
}
cout << q.front() <<' ';
q.pop();
}
cout << q.front();
}
4. 堆
二叉堆
模板 1
int heap[maxn], len = 0;
void push(int x)
{
heap[++len] = x;
int i = len;
while (i > 1 && heap[i] < heap[i / 2])
{
swap(heap[i], heap[i / 2]);
i /= 2;
}
}
void pop()
{
heap[1] = heap[len--];
int i = 1;
while (2 * i <= len)
{
int son = 2 * i;
if (son < len && heap[son + 1] < heap[son])
son++;
if (heap[son] < heap[i])
{
swap(heap[i], heap[son]);
i = son;
}
else
{
break;
}
}
}
模板 2
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
对顶堆
#include <bits/stdc++.h>
using namespace std;
int main()
{
priority_queue<int, vector<int>, greater<int>> a;
priority_queue<int, vector<int>, less<int>> b;
int m, n;
cin >> m >> n;
vector<int> vec(m);
vector<int> u(n);
for (int i = 0; i < m; i++)
cin >> vec[i];
for (int j = 0; j < n; j++)
cin >> u[j];
int idx = 0;
for (int i = 0; i < n; i++)
{
while (idx < u[i])
{
a.push(vec[idx]);
if (!b.empty() && b.top() > a.top())
{
a.push(b.top());
b.pop();
b.push(a.top());
a.pop();
}
idx++;
}
b.push(a.top());
a.pop();
cout << b.top() << endl;
}
}
配对堆
struct Node
{
T v;
Node *child, *sibling;
};
从配对堆的定义可看出,配对堆的根节点的权值一定最小,直接返回根节点即可
Node* meld(Node* x, Node* y) // 合并
{
if (x == nullptr) return y;
if (y == nullptr) return x;
if (x->v > y->v) swap(x, y);
y->sibling = x->child;
x->child = y;
return x;
}
Node* merges(Node* x) // 插入
{
if (x == nullptr || x->sibling == nullptr)
return x;
Node* y = x->sibling;
Node* c = y->sibling;
x->sibling = y->sibling = nullptr;
return meld(merges(c), meld(x, y));
}
Node* delete_min(Node* x) // 删除最小值
{
Node* t = merges(x->child);
delete x;
return t;
}
// 减小一个元素的值
struct Node
{
LL v;
int id;
Node *child, *sibling;
Node *father;
};
Node* meld(Node* x, Node* y)
{
if (x == nullptr) return y;
if (y == nullptr) return x;
if (x->v > y->v) swap(x, y);
if (x->child != nullptr)
x->child->father = y;
y->sibling = x->child;
y->father = x;
x->child = y;
return x;
}
Node *merges(Node *x)
{
if (x == nullptr) return nullptr;
x->father = nullptr;
if (x->sibling == nullptr) return x;
Node *y = x->sibling, *c = y->sibling;
y->father = nullptr;
x->sibling = y->sibling = nullptr;
return meld(merges(c), meld(x, y));
}
Node *decrease_key(Node *root, Node *x, LL v)
{
x->v = v;
if (x == root) return x;
if (x->father->child == x)
x->father->child = x->sibling;
else
x->father->sibling = x->sibling;
if (x->sibling != nullptr)
x->sibling->father = x->father;
x->sibling = nullptr;
x->father = nullptr;
return meld(root, x);
}
左偏树
5. 二叉树和哈夫曼树
性质
- \(结点数=总度数+1\)
- 度为 \(m\) 的树,\(m\) 叉树的区别:树的度——各结点的度的最大值,\(m\) 叉树——每个结点最多只能有 \(m\) 个孩子的树
- 度为 \(m\) 的树第 \(i\) 层至多有 \(m^{i-1}\) 个结点 \((i>=1)\)
- 高度为 \(h\) 的 \(m\) 叉树至多有 \(\frac{m^h-1}{m-1}\) 个结点
- 高度为 \(h\) 的 \(m\) 叉树至少有 \(h\) 个结点,高度为 \(h\) 、度为 \(m\) 的树至少有 \(h+m-1\) 个结点
- 具有 \(n\) 个结点的 \(m\) 叉树的最小高度为 \(log_m(n(m-1)+1)\)
- 设非空二叉树中度为 0、1 和 2 的结点个数分别为 \(n_0、n_1、n_2\) ,则 \(n_0=n_2+1\) (证明:假设树中结点总数为 \(n\),则 \(n=n_0+n_1+n_2\),\(n=n_1+2n_2+1(树的结点总数=总度数+1)\) ,\(后-前\) 得: \(n_0=n_2+1\) )
- 二叉树第 \(i\) 层至多有 \(2^{i-1}\) 个结点 \((i>=1)\),\(m\) 叉树第 \(i\) 层至多有 \(m^{i-1}\) 个结点 \((i>=1)\)
- 高度为 \(h\) 的二叉树至多有 \(2^h-1\) 个结点(满二叉树),高度为 \(m\) 的二叉树至多有 \(\frac{m^h-1}{m-1}\) 个结点
特殊的二叉树
满二叉树
一棵高度为 \(h\),且含有 \(2^h-1\) 个结点的二叉树
特点:
- 只有最后一层有叶子节点
- 不存在度为 \(1\) 的结点
- 按层序从 \(1\) 开始编号,结点 \(i\) 的左孩子为 \(2i\) ,右孩子为 \(2i+1\) ;结点 \(i\) 的父节点为 \(\lfloor \frac{i}{2} \rfloor\) \((如果有的话)\)
完全二叉树
当且仅当其每个结点都与高度为 \(h\) 的满二叉树中编号为 \(1\sim n\) 的结点一一对应时,称为完全二叉树
特点
- 只有最后两层可能有叶子结点
- 最多只有一个度为 \(1\) 的结点
- 按层序从 \(1\) 开始编号,结点 \(i\) 的左孩子为 \(2i\) ,右孩子为 \(2i+1\) ;结点 \(i\) 的父节点为 \(\lfloor \frac{i}{2} \rfloor\) \((如果有的话)\)
- \(i<=\lfloor \frac{n}{2} \rfloor\) 为分支结点,\(i>\lfloor \frac{n}{2} \rfloor\) 为叶子结点
性质
- 具有 \(n\) 个 \((n>0)\) 结点的完全二叉树的高度 \(h\) 为 \(\lceil {log_2{(n+1)}} \rceil\) 或 \(\lfloor {log_2 n} \rfloor +1\)
高为 \(h\) 的满二叉树共有 \(2^h-1\) 个结点
高为 \(h-1\) 的满二叉树共有 \(2^{h-1}-1\) 个结点
高为 \(h-1\) 的完全二叉树至少 \(2^{h-1}\) 个结点,至多 \(2^h-1\) 个结点 - 对于完全二叉树,可以由结点的度数 \(n\) 推出度为 \(0、1、2\) 的结点个数为 \(n_0、n_1、n_2\)
完全二叉树最多只有一个度为 \(1\) 的结点,即 \(n_1=0或1\) ,\(n_0=n_2+1\;(n_0+n_2一定是奇数)\)
若完全二叉树有 \(2k(偶数)\) 个
二叉排序树
一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
左子树上所有结点的关键字均小于根节点的关键字;
右子树上所有结点的关键字均大于根节点的关键字。
左子树和右子树又各是一棵二叉排序树。
平衡二叉树
树上任意结点的左子树和右子树的深度之差不超过 1 。
例题
给出一棵二叉树的中序与后序排列。求出它的先序排列
void beford(string in, string after)
{
if (in.size() > 0)
{
char ch = after[after.size() - 1];
cout << ch; // 找根输出
int k = in.find(ch);
beford(in.substr(0, k), after.substr(0, k));
beford(in.substr(k + 1), after.substr(k, in.size() - k - 1)); // 递归左右子树;
}
}
int main()
{
string inord, aftord;
cin >> inord;
cin >> aftord; // 读入
beford(inord, aftord);
cout << endl;
return 0;
}
string s1, s2;
int find(char ch)
{
for (int i = 0; i < s1.length(); i++)
if (s1[i] == ch)
return i;
}
void dfs(int l1, int r1, int l2, int r2)
{
int m = find(s2[r2]);
cout << s2[r2];
if (m > l1)
dfs(l1, m - 1, l2, r2 - r1 + m - 1);
if (m < r1)
dfs(m + 1, r1, l2 + m - l1, r2 - 1);
}
void solve()
{
cin >> s1 >> s2;
dfs(0, s1.length() - 1, 0, s2.length() - 1);
}
高级数据结构
1. ST 算法
int n, m;
int a[N], dp_max[N][25], dp_min[N][25];
int LOG2[N];
void st_init()
{
LOG2[0] = -1;
for (int i = 1; i <= N; i++)
LOG2[i] = LOG2[i >> 1] + 1;
for (int i = 1; i <= n; i++)
{
dp_min[i][0] = a[i];
dp_max[i][0] = a[i];
}
//int p = (int)log((double)n) / log(2.0);
int p = LOG2[n];
for (int k = 1; k <= p; k++)
{
for (int s = 1; s + (1 << k) - 1 <= n; s++)
{
dp_max[s][k] = max(dp_max[s][k - 1], dp_max[s + (1 << (k - 1))][k - 1]);
dp_min[s][k] = min(dp_min[s][k - 1], dp_min[s + (1 << (k - 1))][k - 1]);
}
}
}
int st_query(int l, int r)
{
int k = LOG2[r - l + 1];
int x = max(dp_max[l][k], dp_max[r - (1 << k) + 1][k]);
int y = min(dp_min[l][k], dp_min[r - (1 << k) + 1][k]);
return x - y;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
st_init();
for (int i = 1; i <= m; i++)
{
int l, r;
cin >> l >> r;
cout << st_query(l, r) << endl;
}
return 0;
}
2. 哈希表
int hash(int x)
{
return (x % N + N) % N; // 哈希函数,确保非负
}
拉链法
模板一
const int N = 1e5 + 3; // 大于数据范围的第一个质数,取质数冲突的概率最小
int h[N], e[N], ne[N], idx;
void init()
{
memset(h, -1, sizeof h);
idx = 0;
// h[0] = -1 , ne[0] = h[0] = -1 , h[0] = idx = 0;
// h[0] = 0 , ne[1] = h[0] = 0 , h[0] = idx = 1;
}
void insert(int x)
{
int k = (x % N + N) % N; // 因为C++ 中的 % 运算无法将负数转为正数
e[idx] = x;
ne[idx] = h[k];
h[k] = idx++;
}
bool find(int x) // 在哈希表中查询某个数是否存在
{
int k = (x % N + N) % N;
for (int i = h[k]; i!=-1; i = ne[i])
{
if (e[i] == x)
return true;
}
return false;
}
void remove(int x)
{
int k = (x % N + N) % N;
int i = h[k], prev = -1;
while (i != -1)
{
if (e[i] == x)
{
if (prev == -1)
{
h[k] = ne[i]; // 头节点即为要删除的节点
}
else
{
ne[prev] = ne[i]; // 其他节点中删除
}
return;
}
prev = i;
i = ne[i];
}
}
模板二
int h[N], e[N], ne[N], idx;
// 向哈希表中插入一个数
void insert(int x)
{
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++ ;
}
// 在哈希表中查询某个数是否存在
bool find(int x)
{
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i])
if (e[i] == x)
return true;
return false;
}
开放寻址法
模板一
const int N = 2e5 + 3; // 一般开放寻址法要开到原数组的2~3倍
const int null = 0x3f3f3f3f; // 用大于10^9的数来代表空
int h[N];
void init()
{
memset(h, 0x3f, sizeof h); // memset是按字节memset的,所以不能直接memset为null;
//for (int i = 0; i < N; i++)
// h[i] = null;
// fill(h, h + N, null);
}
int find(int x) // 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
{
int k = (x % N + N) % N;
while (h[k] != null && h[k] != x)
{
k++;
if (k == N)
k = 0;
}
return k;
}
bool contains(int x) // 判断元素是否存在
{
int k = find(x);
return h[k] == x;
}
void insert(int x)
{
int k = find(x);
if (h[k] == null)
{
h[k] = x;
}
}
void remove(int x)
{
int k = find(x);
if (h[k] == x)
{
h[k] = null;
}
}
模板二
int h[N];
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t ++ ;
if (t == N) t = 0;
}
return t;
}
3. 分块与莫队算法
分块
分块入门 1
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,单点查值
int n, block, t;
int a[N], L[N], R[N], pos[N], atag[N];
void init()
{
block = sqrt(n);
t = n / block;
if (n % block) t++;
for (int i = 1; i <= t; i++)
{
L[i] = (i - 1) * block + 1;
R[i] = i * block;
}
R[t] = n;
for (int i = 1; i <= n; i++) pos[i] = (i - 1) / block + 1;
}
void add(int l, int r, int d)
{
int p = pos[l], q = pos[r];
if(p == q)
for(int i = l; i <= r; i++) a[i] += d;
else
{
for(int i = p + 1; i <= q - 1; i++) atag[i] += d;
for(int i = l; i <= R[p]; i++) a[i] += d;
for(int i = L[q]; i <= r; i++) a[i] += d;
}
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
init();
for(int i = 1; i <= n; i++)
{
int op, l, r, d;
cin >> op >> l >> r >> d;
if(!op) add(l, r, d);
else
{
int q = pos[r];
cout << a[r] + atag[q] << endl;
}
}
}
分块入门 2
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的元素个数。
int n, block, t;
int a[N], L[N], R[N], pos[N], atag[N];
vector<int> vec[N];
void init()
{
block = sqrt(n);
t = n / block;
if (n % block) t++;
for (int i = 1; i <= t; i++)
{
L[i] = (i - 1) * block + 1;
R[i] = i * block;
}
R[t] = n;
for (int i = 1; i <= n; i++)
{
pos[i] = (i - 1) / block + 1;
vec[pos[i]].push_back(a[i]);
}
for (int i = 1; i <= t; i++)
sort(vec[i].begin(), vec[i].end());
}
void reset(int x)
{
vec[x].clear();
for (int i = L[x]; i <= R[x]; i++) vec[x].push_back(a[i]);
sort(vec[x].begin(), vec[x].end());
}
void add(int l, int r, int d)
{
int p = pos[l], q = pos[r];
if (p == q)
{
for (int i = l; i <= r; i++) a[i] += d;
reset(p);
}
else
{
for (int i = p + 1; i <= q - 1; i++) atag[i] += d;
for (int i = l; i <= R[p]; i++) a[i] += d;
reset(p);
for (int i = L[q]; i <= r; i++) a[i] += d;
reset(q);
}
}
int query(int l, int r, int d)
{
int p = pos[l], q = pos[r];
int res = 0;
if (p == q)
{
for (int i = l; i <= r; i++)
if (a[i] + atag[p] < d) res++;
}
else
{
for (int i = p + 1; i <= q - 1; i++)
{
int x = d - atag[i];
res += lower_bound(vec[i].begin(), vec[i].end(), x) - vec[i].begin();
}
for (int i = l; i <= R[p]; i++)
if (a[i] + atag[p] < d) res++;
for (int i = L[q]; i <= r; i++)
if (a[i] + atag[q] < d) res++;
}
return res;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
init();
for (int i = 1; i <= n; i++)
{
int op, l, r, d;
cin >> op >> l >> r >> d;
if (!op) add(l, r, d);
else cout << query(l, r, d * d) << endl;
}
}
int n, a[N];
int st[N], ed[N], pos[N];
int add[N], sum[N]; // add:第 i 块的增量标记, sum:第 i 块的区间和
void init()
{
int block = sqrt(n); // 块的大小:每块有 block 个元素
int t = n / block; // 块的数量:共分为 t 块
if (n % block) // sqrt(n)的结果不是整数,最后加一小块
t++;
for (int i = 1; i <= t; i++) // 遍历块
{
st[i] = (i - 1) * block + 1;
ed[i] = i * block;
}
ed[t] = n; // sqrt(n)的结果不是整数,最后一块较小
for (int i = 1; i <= n; i++) // 遍历所有元素的位置
pos[i] = (i - 1) / block + 1;
for (int i = 1; i <= t; i++)
for (int j = st[i]; j <= ed[i]; j++)
sum[i] += a[j];
}
// 区间修改
void change(int l, int r, int d)
{
int p = pos[l], q = pos[r];
if (p == q) // 情况1:[l, r]在某个块内,即[l, r]是一个碎片
{
for (int i = l; i <= r; i++)
sum[p] += d * (r - l + 1);
}
else // 情况2:[l, r]跨越多个块
{
for (int i = p + 1; i <= q - 1; i++) // 整块
add[i] += d;
for (int i = l; i <= ed[p]; i++) // 整理前面的碎片
a[i] += d;
sum[p] += d * (ed[p] - l + 1);
for (int i = st[i]; i <= r; i++) // 整理后面的碎片
a[i] += d;
sum[q] += d * (r - st[q] + 1);
}
}
// 区间查询
int ask(int l, int r)
{
int p = pos[l], q = pos[r];
int res = 0;
if (p == q) // 情况1:[l, r]在某个块内,即[l, r]是一个碎片
{
for (int i = l; i <= r; i++)
res += a[i];
res += add[p] * (r - l + 1);
}
else
{
for (int i = p + 1; i <= q - 1; i++) // 整块
res += sum[i] + add[i] * (ed[i] - st[i] + 1);
for (int i = l; i <= ed[p]; i++) // 整理前面的碎片
res += a[i];
res += add[p] * (ed[p] - l + 1);
for (int i = st[q]; i <= r; i++) // 整理后面的碎片
res += a[i];
res += add[q] * (r - st[q] + 1);
}
return res;
}
P2801 教主的魔法 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
const int N = 1e6 + 10;
int block, t;
int n, q;
int a[N], add[N];
vector<int> pos[N];
void init()
{
block = sqrt(n);
int t = (n + block - 1) / block;
for (int i = 0; i < n; i++)
{
int p = i / block;
pos[p].push_back(a[i]);
}
for (int i = 0; i < t; i++)
sort(pos[i].begin(), pos[i].end());
}
void update(int k)
{
pos[k].clear();
for (int i = k * block; i < min(n, (k + 1) * block); i++)
{
a[i] += add[k];
pos[k].push_back(a[i]);
}
add[k] = 0;
sort(pos[k].begin(), pos[k].end());
}
void change(int l, int r, int d)
{
int p = l / block, q = r / block;
if (p == q)
{
for (int i = l; i <= r; i++)
a[i] += d;
update(p);
}
else
{
for (int i = p + 1; i <= q - 1; i++)
add[i] += d;
for (int i = l; i < (p + 1) * block; i++)
a[i] += d;
update(p);
for (int i = q * block; i <= r; i++)
a[i] += d;
update(q);
}
}
int ask(int l, int r, int d)
{
int p = l / block, q = r / block;
int res = 0;
if (p == q)
{
for (int i = l; i <= r; i++)
if (a[i] + add[p] >= d)
res++;
}
else
{
for (int i = p + 1; i <= q - 1; i++)
{
auto it = lower_bound(pos[i].begin(), pos[i].end(), d - add[i]);
res += distance(it, pos[i].end());
}
for (int i = l; i <(p+1)*block; i++)
if (a[i] + add[p] >= d)
res++;
for (int i = q*block; i <= r; i++)
if (a[i] + add[q] >= d)
res++;
}
return res;
}
int main()
{
cin >> n >> q;
for (int i = 0; i < n; i++)
cin >> a[i];
init();
char ch;
int l, r, d;
while (q--)
{
cin >> ch >> l >> r >> d;
l--;
r--;
if (ch == 'A')
cout << ask(l, r, d) << endl;
else
change(l, r, d);
}
}
莫队算法
基础莫队
洛谷P1972
数据加强后,莫队已经过不了了
struct node
{
int l, r, k; // k为查询操作的原始顺序
} q[N];
int n, m;
int a[N], res; // res 存储当前区间内不同数字的个数
int pos[N], ans[N], cnt[N];
bool cmp(node &a, node &b)
{
// 按块排序,就是莫队算法
if (pos[a.l] != pos[b.l]) // 按 l 所在块排序,若块相等,再按 r 排序
return pos[a.l] < pos[b.l];
if (pos[a.l] & 1) // 奇偶性优化
return a.r > b.r;
return a.r < b.r;
}
void add(int x) // 扩大区间时( l 左移, r 右移 ),增加 x 出现次数
{
cnt[a[x]]++;
if (cnt[a[x]] == 1) // 这个元素第一次出现
res++;
}
void del(int x)
{
cnt[a[x]]--;
if (cnt[a[x]] == 0) // 这个元素消失了
res--;
}
int main()
{
scanf("%d", &n);
int block = sqrt(n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
pos[i] = (i - 1) / block + 1;
}
scanf("%d", &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &q[i].l, &q[i].r);
q[i].k = i;
}
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0; // 重要
for (int i = 1; i <= m; i++)
{
// 缩小区间
while (l < q[i].l) del(l++);// l 右移
while (r > q[i].r) del(r--);// r 左移
// 扩大区间
while (l > q[i].l) add(--l);// l 左移
while (r < q[i].r) add(++r);// r 右移
ans[q[i].k] = res;
}
for (int i = 1; i <= m; i++)
printf("%d\n", ans[i]);
}
4. 并查集
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
(2)维护size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
标准并查集
int h[N]; //树的高度
int s[N];
int n, m, ans;
void init()
{
for (int i = 1; i <= N; i++)
{
s[i] = i;
h[i] = 0;
}
}
int find(int x)
{
if(x!=s[x]) x = find(s[x]);
return s[x];
}
void merge(int x, int y)
{
x = find(x);
y = find(y);
if (h[x] == h[y])
{
h[x]++;
s[y] = x;
}
else
{
if(h[x]<h[y]) s[x] = y;
else s[y] = x;
}
}
find 的非递归代码
int find(int x)
{
int r = x;
while(s[r]!=r)
r = s[r]; //找到根节点
int i = x, j;
while(i!=r)
{
j = s[i]; //用临时变量j记录
s[i] = r; //把路径上元素的集改为根节点
i = j;
}
return r;
}
扩展域并查集
int s[N*3], h[N*3];
int n, m;
void init()
{
for (int i = 1; i <= n; i++)
{
s[i] = i, h[i] = 0;
s[i + n] = i + n, h[i + n] = 0;
s[i + 2 * n] = i + 2 * n, h[i + 2 * n] = 0;
}
}
int find(int x)
{
if (s[x] != x)
s[x] = find(s[x]);
return s[x];
}
void merge(int x, int y)
{
x = find(x);
y = find(y);
if (h[x] == h[y])
{
h[x]++;
s[y] = x;
}
else
{
if (h[x] < h[y])
s[x] = y;
else
s[y] = x;
}
}
bool same(int x, int y)
{
return find(x) == find(y);
}
int main()
{
cin >> n >> m;
init();
int ans = 0;
for (int i = 1; i <= m; i++)
{
int op, x, y;
cin >> op >> x >> y;
if (x > n || y > n)
{
ans++;
continue;
}
if (op == 2 && x == y)
{
ans++;
continue;
}
if (op == 1)
{
if (same(x, y + n) || same(x, y + 2 * n))
{
ans++;
continue;
}
merge(x, y);
merge(x + n, y + n);
merge(x + 2 * n, y + 2 * n);
}
else
{
if (same(x, y) || same(x, y + 2 * n))
{
ans++;
continue;
}
merge(x, y + n);
merge(x + n, y + 2 * n);
merge(x + 2 * n, y);
}
}
cout << ans << endl;
}
带权并查集
int s[N];
int d[N]; //权值
int n, m, ans;
void init()
{
for (int i = 0; i <= n;i++)
{
s[i] = i;
d[i] = 0;
}
}
int find(int x)
{
if(x != s[x])
{
int t = s[x];
s[x] = find(s[x]);
d[x] += d[t];
}
return s[x];
}
void merge(int a, int b, int v)
{
int roota = find(a), rootb = find(b);
if(roota==rootb)
{
if(d[a]-d[b]!=v)
ans++;
}
else
{
s[roota] = rootb;
d[roota] = d[b] + v - d[a];
}
}
int main()
{
while(~scanf("%d %d",&n,&m))
{
ans = 0;
init();
while(m--)
{
int a, b, v;
scanf("%d %d %d", &a, &b, &v);
a--;
merge(a, b, v);
}
printf("%d\n", ans);
}
}
5. 树状数组
一维树状数组
单点修改 + 区间查询
lowbit(x) = x & -x //找到 x 的二进制数的最后一个 1 和后面所有 0 组成的 2^k
int c[N];
void update(int k, int v) //单点修改
{
for (int i = k; i <= n; i += lowbit(i))
c[i] += v;
}
int get(int k) // 前缀和: sum=a[1]+a[2]+...+a[k]
{
int res = 0;
for (int i = k; i; i -= lowbit(i))
res += c[i];
return res;
}
int ask(int l, int r) //区间查询
{
return get(r) - get(l - 1);
}
区间修改 + 单点查询
int d[N];
void update(int k, int v)
{
for (int i = k; i <= n; i += lowbit(i))
d[i] += v;
}
void update1(int l, int r, int v) // 区间修改
{
update(l, v), update(r + 1, -v);
}
int get(int k)
{
int res = 0;
for (int i = k; i; i -= lowbit(i))
res += d[i];
return res;
}
int ask(int k) // 单点查询
{
return get(k);
}
区间修改 + 区间查询
int d1[N], d2[N];
void update(int k, int v)
{
for(int i = k; i <= n; i += lowbit(i))
d1[i] += v, d2[i] += k * v;
}
void update1(int l, int r, int v) // 区间修改
{
update(l, v), update(r + 1, -v); // 将区间加 差分 为两个前缀加
}
int get(int k)
{
int res = 0;
for(int i = k; i; i -= lowbit(i))
res += (k + 1ll) * d1[i] - d2[i];
return res;
}
int ask(int l, int r) // 区间查询
{
return get(r) - get(l - 1);
}
二维树状数组
单点修改 + 区间查询
void update(int x, int y, int v)
{
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j))
c[i][j] += v;
}
int get(int x, int y)
{
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
res += c[i][j];
return res;
}
int ask(int x1, int y1, int x2, int y2) // 查询子矩阵和
{
return sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1);
}
区间修改 + 单点查询
void update(int x, int y, int v)
{
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j))
d[i][j] += v;
}
void update1(int xa, int ya, int xb, int yb, int v)
{
update(xa, ya, v);
update(xa, yb + 1, -v);
update(xb + 1, ya, -v);
update(xb + 1, yb + 1, v);
}
int get(int x, int y)
{
int res = 0;
for(int i = x; i; i -= lowbit(i))
for(int j = y; j; j -= lowbit(j))
res += d[i][j];
}
区间修改 + 区间查询
int d1[N][N], d2[N][N], d3[N][N], d4[N][N];
void update(int x, int y, int v)
{
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j))
{
d1[i][j] += v;
d2[i][j] += v * x;
d3[i][j] += v * y;
d4[i][j] += v * x * y;
}
}
void update1(int xa, int ya, int xb, int yb, int v)
{
//(xa, ya) 到 (xb, yb) 子矩阵
update(xa, ya, v);
update(xa, yb + 1, -v);
update(xb + 1, ya, -v);
update(xb + 1, yb + 1, v);
}
int get(int x, int y)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
for (int j = y; j; j -= lowbit(j))
res += (x + 1) * (y + 1) * d1[i][j] - (y + 1) * d2[i][j] - (x + 1) * d3[i][j] + d4[i][j];
return res;
}
int ask(int xa, int ya, int xb, int yb)
{
return get(xb, yb) - get(xb, ya - 1) - get(xa - 1, yb) + get(xa - 1, ya - 1);
}
权值树状数组
查询全局第 k 小
// 权值树状数组查询第 k 小
int kth(int k)
{
int sum = 0, x = 0;
for (int i = log2(n); ~i; --i)
{
x += 1 << i; // 尝试扩展
if (x >= n || sum + c[x] >= k) // 如果扩展失败
x -= 1 << i;
else
sum += c[x];
}
return x + 1;
}
全局逆序对
维护不可差分信息
int getmax(int l, int r) // 区间查询
{
int res = 0;
while (r >= l)
{
res = max(res, a[r]);
--r;
for (; r - lowbit(r) >= l; r -= lowbit(r))
res = max(res, c[r]);
}
return res;
}
void update(int k, int v) // 单点更新
{
a[k] = v;
for (int i = k; i <= n; i += lowbit(i))
{
// 枚举受影响的区间
c[i] = a[i];
for (int j = 1; j < lowbit(i); j *= 2)
c[i] = max(c[i], c[i - j]);
}
}
建树
法一
// Θ(n) 建树
void init()
{
for (int i = 1; i <= n; ++i)
{
c[i] += a[i];
int j = i + lowbit(i);
if (j <= n) c[j] += c[i];
}
}
法二
先预处理一个 sum 前缀和数组
// Θ(n) 建树
void init()
{
for (int i = 1; i <= n; ++i)
t[i] = sum[i] - sum[i - lowbit(i)];
}
时间戳优化
// 时间戳优化
int tag[N], t[N], Tag;
void reset() { ++Tag; }
void add(int k, int v)
{
for(int i = k; i <= n; i += lowbit(i))
{
if(tag[i] != Tag) c[i] = 0;
c[i] += v, tag[i] = Tag;
}
}
int getsum(int k)
{
int res = 0;
for(int i = k; i; i -= lowbit(i))
if(tag[i] == Tag)
res += c[i];
return res;
}
6. 线段树
模板一
int a[N];
int tree[N << 2];
int tag[N << 2];
int ls(int p) { return p << 1; }
int rs(int p) { return p << 1 | 1; }
void addtag(int p, int pl, int pr, int d)
{
tag[p] += d;
tree[p] += d * (pr - pl + 1);
}
void pushup(int p)
{
tree[p] = tree[ls(p)] + tree[rs(p)];
}
void pushdown(int p, int pl, int pr)
{
if (tag[p])
{
int mid = (pl + pr) >> 1;
addtag(ls(p), pl, mid, tag[p]);
addtag(rs(p), mid + 1, pr, tag[p]);
tag[p] = 0;
}
}
void build(int p, int pl, int pr)
{
tag[p] = 0;
if (pl == pr)
{
tree[p] = a[pl];
return;
}
int mid = (pl + pr) >> 1;
build(ls(p), pl, mid);
build(rs(p), mid + 1, pr);
pushup(p);
}
void update(int l, int r, int p, int pl, int pr, int d)
{
if (l <= pl && pr <= r)
{
addtag(p, pl, pr, d);
return;
}
pushdown(p, pl, pr);
int mid = (pl + pr) >> 1;
if (l <= mid) update(l, r, ls(p), pl, mid, d);
if (r > mid) update(l, r, rs(p), mid + 1, pr, d);
pushup(p);
}
int query(int l, int r, int p, int pl, int pr)
{
if (l <= pl && pr <= r)
return tree[p];
pushdown(p, pl, pr);
int res = 0;
int mid = (pl + pr) >> 1;
if (l <= mid) res += query(l, r, ls(p), pl, mid);
if (r > mid) res += query(l, r, rs(p), mid + 1, pr);
return res;
}
signed main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
while (m--)
{
int op, l, r, d;
cin >> op;
if (op == 1)
{
cin >> l >> r >> d;
update(l, r, 1, 1, n, d);
}
else
{
cin >> l >> r;
cout << query(l, r, 1, 1, n) << endl;
}
}
return 0;
}
模板二
建树
int d[4*n];
void build(int s, int t, int p) // 建树
{
if (s == t)
{
d[p] = a[s];
return;
}
int m = s + ((t - s) >> 1);
build(s, m, p << 1), build(m + 1, t, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
build(1, n, 1);
区间修改与区间查询
getsum(l, r, 1, n, 1);
update(l, r, c, 1, n, 1);
区间加值与区间求和
void update(int l, int r, int c, int s, int t, int p) // 区间修改
{
if (l <= s && t <= r)
{
d[p] += (t - s + 1) * c, b[p] += c;
return;
}
int m = s + ((t - s) >> 1);
if (b[p])
{
d[p << 1] += b[p] * (m - s + 1), d[(p << 1) | 1] += b[p] * (t - m);
b[p << 1] += b[p], b[(p << 1) | 1] += b[p];
b[p] = 0;
}
if (l <= m) update(l, r, c, s, m, p << 1);
if (r > m) update(l, r, c, m + 1, t, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
int getsum(int l, int r, int s, int t, int p) // 区间查询
{
if (l <= s && t <= r)
return d[p];
int m = s + ((t - s) >> 1);
if (b[p])
{
d[p << 1] += b[p] * (m - s + 1), d[(p << 1) | 1] += b[p] * (t - m);
b[p << 1] += b[p], b[(p << 1) | 1] += b[p];
b[p] = 0;
}
int res = 0;
if (l <= m) res += getsum(l, r, s, m, p << 1);
if (r > m) res += getsum(l, r, m + 1, t, (p << 1) | 1);
return res;
}
区间修改值与区间求和
void update(int l, int r, int c, int s, int t, int p) // 区间修改
{
if (l <= s && t <= r)
{
d[p] = (t - s + 1) * c, b[p] = c, v[p] = 1;
return;
}
int m = s + ((t - s) >> 1);
if (v[p])
{
d[p << 1] = b[p] * (m - s + 1), d[(p << 1) | 1] = b[p] * (t - m);
b[p << 1] = b[(p << 1) | 1] = b[p];
v[p << 1] = v[(p << 1) | 1] = 1;
v[p] = 0;
}
if (l <= m) update(l, r, c, s, m, p << 1);
if (r > m) update(l, r, c, m + 1, t, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
int getsum(int l, int r, int s, int t, int p) // 区间查询
{
if (l <= s && t <= r)
return d[p];
int m = s + ((t - s) >> 1);
if (v[p])
{
d[p << 1] = b[p] * (m - s + 1), d[(p << 1) | 1] = b[p] * (t - m);
b[p << 1] = b[(p << 1) | 1] = b[p];
v[p << 1] = v[(p << 1) | 1] = 1;
v[p] = 0;
}
int res = 0;
if (l <= m) res = getsum(l, r, s, m, p << 1);
if (r > m) res += getsum(l, r, m + 1, t, (p << 1) | 1);
return res;
}
区间加乘与区间查询
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int n, q, mod;
int a[N], sum[N << 2], mul[N << 2], lazy[N << 2];
void pushup(int p)
{
sum[p] = (sum[p << 1] + sum[(p << 1) | 1]) % mod;
}
void pushdown(int p, int s, int t)
{
int m = s + ((t - s) >> 1);
int l = p << 1, r = (p << 1) | 1;
// 先处理乘法标记
if (mul[p] != 1)
{
mul[l] = (mul[l] * mul[p]) % mod;
mul[r] = (mul[r] * mul[p]) % mod;
lazy[l] = (lazy[l] * mul[p]) % mod;
lazy[r] = (lazy[r] * mul[p]) % mod;
sum[l] = (sum[l] * mul[p]) % mod;
sum[r] = (sum[r] * mul[p]) % mod;
mul[p] = 1;
}
// 再处理加法标记
if (lazy[p])
{
sum[l] = (sum[l] + lazy[p] * (m - s + 1)) % mod;
sum[r] = (sum[r] + lazy[p] * (t - m)) % mod;
lazy[l] = (lazy[l] + lazy[p]) % mod;
lazy[r] = (lazy[r] + lazy[p]) % mod;
lazy[p] = 0;
}
}
void build(int s, int t, int p)
{
mul[p] = 1;
lazy[p] = 0;
if (s == t)
{
sum[p] = a[s] % mod;
return;
}
int m = s + ((t - s) >> 1);
build(s, m, p << 1);
build(m + 1, t, (p << 1) | 1);
pushup(p);
}
void chen(int l, int r, int s, int t, int p, int k)
{
if (l <= s && t <= r)
{
mul[p] = (mul[p] * k) % mod;
lazy[p] = (lazy[p] * k) % mod;
sum[p] = (sum[p] * k) % mod;
return;
}
pushdown(p, s, t);
int m = s + ((t - s) >> 1);
if (l <= m) chen(l, r, s, m, p << 1, k);
if (r > m) chen(l, r, m + 1, t, (p << 1) | 1, k);
pushup(p);
}
void add(int l, int r, int s, int t, int p, int k)
{
if (l <= s && t <= r)
{
sum[p] = (sum[p] + k * (t - s + 1)) % mod;
lazy[p] = (lazy[p] + k) % mod;
return;
}
pushdown(p, s, t);
int m = s + ((t - s) >> 1);
if (l <= m) add(l, r, s, m, p << 1, k);
if (r > m) add(l, r, m + 1, t, (p << 1) | 1, k);
pushup(p);
}
int getsum(int l, int r, int s, int t, int p)
{
if (l <= s && t <= r)
return sum[p];
pushdown(p, s, t);
int m = s + ((t - s) >> 1);
int res = 0;
if (l <= m) res = (res + getsum(l, r, s, m, p << 1)) % mod;
if (r > m) res = (res + getsum(l, r, m + 1, t, (p << 1) | 1)) % mod;
return res;
}
signed main()
{
cin >> n >> q >> mod;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, n, 1);
while (q--)
{
int op, x, y, k;
cin >> op;
if (op == 1)
{
cin >> x >> y >> k;
chen(x, y, 1, n, 1, k);
}
else if (op == 2)
{
cin >> x >> y >> k;
add(x, y, 1, n, 1, k);
}
else
{
cin >> x >> y;
cout << getsum(x, y, 1, n, 1) << endl;
}
}
return 0;
}
动态开点线段树
int n, cnt, root;
int sum[n * 2], ls[n * 2], rs[n * 2];
// 用法:update(root, 1, n, x, f); 其中 x 为待修改节点的编号
void update(int& p, int s, int t, int x, int f) // 单点修改
{
if (!p) p = ++cnt;
if (s == t)
{
sum[p] += f;
return;
}
int m = s + ((t - s) >> 1);
if (x <= m) update(ls[p], s, m, x, f);
else update(rs[p], m + 1, t, x, f);
sum[p] = sum[ls[p]] + sum[rs[p]]; // pushup
}
// 用法:query(root, 1, n, l, r);
int query(int p, int s, int t, int l, int r) // 区间查询
{
if (!p) return 0;
if (s >= l && t <= r) return sum[p];
int m = s + ((t - s) >> 1), res = 0;
if (l <= m) res += query(ls[p], s, m, l, r);
if (r > m) res += query(rs[p], m + 1, t, l, r);
return res;
}
区间修改也是一样的,不过下放标记时要注意如果缺少孩子,就直接创建一个新的孩子。或者使用标记永久化技巧。
其它操作跟线段树是一样的,只要把普通线段树里的 p<<1 换成ls[p],p<<1|1 换成 rs[p] 就行了。
一些优化
这里总结几个线段树的优化:
-
在叶子节点处无需下放懒惰标记,所以懒惰标记可以不下传到叶子节点。
-
下放懒惰标记可以写一个专门的函数
pushdown,从儿子节点更新当前节点也可以写一个专门的函数maintain(或者对称地用pushup),降低代码编写难度。 -
标记永久化:如果确定懒惰标记不会在中途被加到溢出(即超过了该类型数据所能表示的最大范围),那么就可以将标记永久化。标记永久化可以避免下传懒惰标记,只需在进行询问时把标记的影响加到答案当中,从而降低程序常数。具体如何处理与题目特性相关,需结合题目来写。这也是树套树和可持久化数据结构中会用到的一种技巧。
封装模板
区间加/区间求和
#include <bits/stdc++.h>
using namespace std;
template <typename T>
class SegTreeLazyRangeAdd
{
vector<T> tree, lazy;
vector<T> *arr;
int n, root, n4, end;
void maintain(int cl, int cr, int p)
{
int cm = cl + (cr - cl) / 2;
if (cl != cr && lazy[p])
{
lazy[p * 2] += lazy[p];
lazy[p * 2 + 1] += lazy[p];
tree[p * 2] += lazy[p] * (cm - cl + 1);
tree[p * 2 + 1] += lazy[p] * (cr - cm);
lazy[p] = 0;
}
}
T range_sum(int l, int r, int cl, int cr, int p)
{
if (l <= cl && cr <= r) return tree[p];
int m = cl + (cr - cl) / 2;
T sum = 0;
maintain(cl, cr, p);
if (l <= m) sum += range_sum(l, r, cl, m, p * 2);
if (r > m) sum += range_sum(l, r, m + 1, cr, p * 2 + 1);
return sum;
}
void range_add(int l, int r, T val, int cl, int cr, int p)
{
if (l <= cl && cr <= r)
{
lazy[p] += val;
tree[p] += (cr - cl + 1) * val;
return;
}
int m = cl + (cr - cl) / 2;
maintain(cl, cr, p);
if (l <= m) range_add(l, r, val, cl, m, p * 2);
if (r > m) range_add(l, r, val, m + 1, cr, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
void build(int s, int t, int p)
{
if (s == t)
{
tree[p] = (*arr)[s];
return;
}
int m = s + (t - s) / 2;
build(s, m, p * 2);
build(m + 1, t, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
public:
explicit SegTreeLazyRangeAdd<T>(vector<T> v)
{
n = v.size();
n4 = n * 4;
tree = vector<T>(n4, 0);
lazy = vector<T>(n4, 0);
arr = &v;
end = n - 1;
root = 1;
build(0, end, 1);
arr = nullptr;
}
void show(int p, int depth = 0)
{
if (p > n4 || tree[p] == 0) return;
show(p * 2, depth + 1);
for (int i = 0; i < depth; ++i) putchar('\t');
printf("%d:%d\n", tree[p], lazy[p]);
show(p * 2 + 1, depth + 1);
}
T range_sum(int l, int r)
{
return range_sum(l, r, 0, end, root);
}
void range_add(int l, int r, T val)
{
range_add(l, r, val, 0, end, root);
}
};
区间修改/区间求和
#include <bits/stdc++.h>
using namespace std;
template <typename T>
class SegTreeLazyRangeSet
{
vector<T> tree, lazy;
vector<T> *arr;
vector<bool> ifLazy;
int n, root, n4, end;
void maintain(int cl, int cr, int p)
{
int cm = cl + (cr - cl) / 2;
if (cl != cr && ifLazy[p])
{
lazy[p * 2] = lazy[p], ifLazy[p*2] = 1;
lazy[p * 2 + 1] = lazy[p], ifLazy[p*2+1] = 1;
tree[p * 2] = lazy[p] * (cm - cl + 1);
tree[p * 2 + 1] = lazy[p] * (cr - cm);
lazy[p] = 0;
ifLazy[p] = 0;
}
}
T range_sum(int l, int r, int cl, int cr, int p)
{
if (l <= cl && cr <= r) return tree[p];
int m = cl + (cr - cl) / 2;
T sum = 0;
maintain(cl, cr, p);
if (l <= m) sum += range_sum(l, r, cl, m, p * 2);
if (r > m) sum += range_sum(l, r, m + 1, cr, p * 2 + 1);
return sum;
}
void range_set(int l, int r, T val, int cl, int cr, int p)
{
if (l <= cl && cr <= r)
{
lazy[p] = val;
ifLazy[p] = 1;
tree[p] = (cr - cl + 1) * val;
return;
}
int m = cl + (cr - cl) / 2;
maintain(cl, cr, p);
if (l <= m) range_set(l, r, val, cl, m, p * 2);
if (r > m) range_set(l, r, val, m + 1, cr, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
void build(int s, int t, int p)
{
if (s == t)
{
tree[p] = (*arr)[s];
return;
}
int m = s + (t - s) / 2;
build(s, m, p * 2);
build(m + 1, t, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
public:
explicit SegTreeLazyRangeSet<T>(vector<T> v)
{
n = v.size();
n4 = n * 4;
tree = vector<T>(n4, 0);
lazy = vector<T>(n4, 0);
ifLazy = vector<bool>(n4,0);
arr = &v;
end = n - 1;
root = 1;
build(0, end, 1);
arr = nullptr;
}
void show(int p, int depth = 0)
{
if (p > n4 || tree[p] == 0) return;
show(p * 2, depth + 1);
for (int i = 0; i < depth; ++i) putchar('\t');
printf("%d:%d\n", tree[p], lazy[p]);
show(p * 2 + 1, depth + 1);
}
T range_sum(int l, int r)
{
return range_sum(l, r, 0, end, root);
}
void range_set(int l, int r, T val)
{
range_set(l, r, val, 0, end, root);
}
};

浙公网安备 33010602011771号