数据结构

基础数据结构

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);

对顶堆

P1801 黑匣子 - 洛谷

#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. \(结点数=总度数+1\)
  2. 度为 \(m\) 的树,\(m\) 叉树的区别:树的度——各结点的度的最大值,\(m\) 叉树——每个结点最多只能有 \(m\) 个孩子的树
  3. 度为 \(m\) 的树第 \(i\) 层至多有 \(m^{i-1}\) 个结点 \((i>=1)\)
  4. 高度为 \(h\)\(m\) 叉树至多有 \(\frac{m^h-1}{m-1}\) 个结点
  5. 高度为 \(h\)\(m\) 叉树至少有 \(h\) 个结点,高度为 \(h\) 、度为 \(m\) 的树至少有 \(h+m-1\) 个结点
  6. 具有 \(n\) 个结点的 \(m\) 叉树的最小高度为 \(log_m(n(m-1)+1)\)
  7. 设非空二叉树中度为 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\)
  8. 二叉树第 \(i\) 层至多有 \(2^{i-1}\) 个结点 \((i>=1)\)\(m\) 叉树第 \(i\) 层至多有 \(m^{i-1}\) 个结点 \((i>=1)\)
  9. 高度为 \(h\) 的二叉树至多有 \(2^h-1\) 个结点(满二叉树),高度为 \(m\) 的二叉树至多有 \(\frac{m^h-1}{m-1}\) 个结点

特殊的二叉树

满二叉树

一棵高度为 \(h\),且含有 \(2^h-1\) 个结点的二叉树

特点:

  1. 只有最后一层有叶子节点
  2. 不存在度为 \(1\) 的结点
  3. 按层序从 \(1\) 开始编号,结点 \(i\) 的左孩子为 \(2i\) ,右孩子为 \(2i+1\) ;结点 \(i\) 的父节点为 \(\lfloor \frac{i}{2} \rfloor\) \((如果有的话)\)

完全二叉树

当且仅当其每个结点都与高度为 \(h\) 的满二叉树中编号为 \(1\sim n\) 的结点一一对应时,称为完全二叉树

特点

  1. 只有最后两层可能有叶子结点
  2. 最多只有一个度为 \(1\) 的结点
  3. 按层序从 \(1\) 开始编号,结点 \(i\) 的左孩子为 \(2i\) ,右孩子为 \(2i+1\) ;结点 \(i\) 的父节点为 \(\lfloor \frac{i}{2} \rfloor\) \((如果有的话)\)
  4. \(i<=\lfloor \frac{n}{2} \rfloor\) 为分支结点,\(i>\lfloor \frac{n}{2} \rfloor\) 为叶子结点

性质

  1. 具有 \(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\) 个结点
  2. 对于完全二叉树,可以由结点的度数 \(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. 分块与莫队算法

分块

题库 - LibreOJ (loj.ac)

分块入门 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;
}

扩展域并查集

洛谷P2024

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;
}

带权并查集

hdu 3038

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. 线段树

洛谷P3372【模板】线段树1

模板一

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;
}
区间加乘与区间查询

P3373 【模板】线段树 2 - 洛谷

#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);
	}
};

可持久化线段树

7. K-D 树

8. LCA

9. Treap 树

10. FHQ Treap 树

11. Splay 树

12. 动态树与 LCT

13. 二分查找树

14. 块状链表

15. 替罪羊树

16. 树链剖分

17. 树上的分治

18. 笛卡尔树

19. 简单树上问题

posted @ 2025-07-28 14:36  Aurora_333  阅读(6)  评论(0)    收藏  举报