常用算法代码模板与题单 | C - 数据结构

​欢迎大家订阅我的专栏:算法题解:C++与Python实现*
本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战!

专栏特色
1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的代码实现与详细指导,帮助您夯实算法基础。
2.系统化学习路径:按照算法类别和难度分级,从基础到进阶,循序渐进,帮助您全面提升编程能力与算法思维。

适合人群:

  • 准备参加蓝桥杯、GESP、CSP-J、CSP-S等信息学竞赛的学生
  • 希望系统学习C++/Python编程的初学者
  • 想要提升算法与编程能力的编程爱好者

这篇文档其实是我自己学习路上的一份“复习笔记”。之前看了不少教程,零零散散记了一堆代码片段和小技巧,时间久了就容易忘,翻起来也麻烦。所以干脆花时间整理了一下,把常用的模板、容易踩的坑、还有一些个人觉得挺实用的小技巧都归到了一起。

如果你也在学类似的东西,或许能帮你省下一些翻找的时间;如果有哪里写得不清楚或不对,也欢迎随时指正交流。

希望这份整理也能对你有一点点帮助~ ✨


【队列】

算法模板

手搓队列

int q[N], hh, tt=-1;
// 插入
q[++tt] = x;
// 弹出
hh++;
//判断队列是否为空
if (hh<=tt) not empty
else empty
//取出队头元素
q[hh]

STL队列

queue<int> q;
// 插入
q.push();
// 弹出
q.pop();
// 判断队列是否为空
q.empty();
// 取出队头元素
q.front();

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing829 模拟队列简单队列(数组模拟)用数组 q[N] 模拟队列,维护队首指针 hh(初始为 0 0 0)和队尾指针 tt(初始为 − 1 -1 1),pushq[++tt]=xpophh++query 输出 q[hh]empty 判断 hh<=tt 是否成立。
AcWing6034 周末舞会
AcWing6035 Blah数集
AcWing6036 围圈报数
洛谷AT_abc402_b Restaurant Queue入门
洛谷AT_abc335_c Loong Tracking普及-
洛谷AT_abc413_c Large Queue普及-
洛谷B3616 队列普及-
洛谷[[B4295 报数游戏]]普及-
洛谷[[P2952 Cow Line]]普及-
洛谷P3887 世界杯普及-
洛谷P5661 公交换乘普及-
洛谷[[P11375 树上游走]]普及-
洛谷AT_abc379_d Home Garden普及
洛谷AT_abc391_d Gravity普及
洛谷P2058 海港普及
洛谷P14924 宝石项链普及+

【链表】

算法模板

list基本操作

(1) 获取起始迭代器: it = ls.begin();  // 指向第一个元素的首地址
(2) 获取结束迭代器: it = ls.end();  // 指向最后一个元素的尾地址
(3) 解引用访问数据: int v = *it;  // 获取当前迭代器指向的元素值
(4) 向前/后遍历: it++++it / it----it;  // 移动到下/上一个节点
it = ls.end(), it--;  // it指向名为ls的list的最后一个元素
(5) 移动操作:advance(it, n);  // 将迭代器it向前或向后移动n个位置
    next(it);  // 返回it的下一个位置的迭代器(不修改原迭代器)
    prev(it);  // 返回it的上一个位置的迭代器(不修改原迭代器)
(6) 判断两个迭代器的关系:== / !=;  // 判断两个迭代器是否指向同一元素
注意:链表迭代器不能比较大小
遍历方法一:用迭代器循环遍历链表;
for (list<int>:: iterator it = ls.begin(); it != ls.end(); it++)
    cout << *it << endl;
迭代器类型:list<int>:: iterator是双向链表迭代器,支持++--。
终止条件:it != ls.end() 确保遍历到最后一个有效元素后停止。
前缀递增:++it 或 it++ 即可,表示将迭代器指向下一个元素。
遍历方法二:先定义迭代器,再循环遍历;
list<int>:: iterator it;
for (it = ls.begin(); it != ls.end(); it++)
    cout << *it << endl;
遍历方法三:使用auto自动推导迭代器类型来遍历链表;
for (auto it = ls.begin(); it != ls.end(); it++)
    cout << *it << endl;
遍历方法四:范围循环——自动匹配元素类型,并自动遍历;
for (auto x : ls) cout << x << endl;
(7) ls.push_back(x) / ls.push_front(x): 在链表ls的尾部/头部插入一个元素x;
(8) ls.insert(): 在链表ls中插入元素,返回新插入元素的迭代器;
    ① ls.insert(it, y);  // 在链表ls中it指向的位置之前插入元素y;
    ② ls.insert(it, x, y);  // 在链表ls中it指向的位置之前插入x个元素y;
    ③ ls.insert(it, it1, it2);  // 在链表ls中it指向的位置之前插入[it1, it2]之间的所有元素;
(9) ls.erase(it): 在链表ls中删除it所指向的元素;
    若删除的元素在list的尾部,返回ls.end();
    若删除的元素不再list的尾部,返回下一个元素的迭代器;
(10) ls.empty(): 判断链表ls是否为空;
(11) ls.clear(): 清空链表sl
(12) ls.size(): 返回链表ls中的元素个数;
(13) ls.remove(x): 在链表ls中删除所有等于x的元素;
(14) ls.reverse(): 反转链表ls中元素的顺序;
(15) ls.splice(): 将一个链表或子链表移动到当前链表的执行位置;
    ① 移动整个链表: s.splice(it, t); 将整个链表t都移动到链表s的指定位置it处;
    ② 移动单个元素: s.splice(it1, t, it2); 将链表t中it2指向的元素移动到链表s的指定位置it1处;
    ③ 移动子链表: s.splice(it1, t, it2, it3); 将链表t中[it2, it3)之间的元素移动到链表s的指定位置it1处;

单链表

// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
    head = -1;
    idx = 0;
}
// 在链表头插入一个数a
void add_to_head(int a)
{
    e[idx] = a, ne[idx] = head, head = idx++;
}
// 将x插到下标是k的点后面
void add(int k, int x)
{
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
}
// 将头结点删除,需要保证头结点存在
void remove_head()
{
    head = ne[head];
}
// 将下标是k的点后面的点删掉
void remove(int k)
{
    ne[k] = ne[ne[k]];
}

双链表

// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
    //0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;
}
//在下标是k的点的右边,插入x
void add(int k, int x)
{
    e[idx] = x;
    r[idx] = r[k], l[idx] = k;
    l[r[k]] = idx, r[k] = idx++;
}
//删除第k个点
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing826 单链表简单链表(数组模拟)用数组 e[N] 存储节点值、ne[N] 存储下一个节点下标,维护头指针 head(初始 − 1 -1 1)和可用节点索引 idx(初始 0 0 0),H x 时头插(e[idx]=x, ne[idx]=head, head=idx++),I k x 时在"第 k k k 个插入的节点"后插入(e[idx]=x, ne[idx]=ne[k-1], ne[k-1]=idx++),D k 时删除其后节点(k=0 删头结点,否则 ne[k-1]=ne[ne[k-1]]),最后从头指针遍历输出。
AcWing827 双链表简单双链表(数组模拟)01 作为左右哨兵节点,e[idx] 存值、l[idx]r[idx] 分别存左右指针,插入时在节点 k k k 右侧插入新节点并更新四个指针(r[idx]=r[k], l[idx]=k, l[r[k]]=idx, r[k]=idx),删除时让左右节点互相指向(r[l[k]]=r[k], l[r[k]]=l[k]),L x 在左哨兵右侧插入,R x 在右哨兵左侧插入,IL k x k k k 左侧插入(即 l [ k ] l[k] l[k] 右侧),D k 直接删除第 k k k 个节点,最后从 r[0] 遍历到哨兵 1 1 1 输出。
AcWing4089 小熊的果篮
洛谷[[P10264 接竹竿]]普及+
洛谷P5688 散步省选

【单调队列】

算法模板

struct Node
{
    int v, idx;
};
deque<Node> dq;

for (int i = 1; i <= n; i++)  // 遍历数组
{
    while (!dq.empty() && dq.front().idx <= i - k)
        dq.pop_front();
    // 维护minq队列:保持队列单调递增
	// 如果修改为dq.back().v <= a[i],则维护maxq队列:保持队列单调递减
    while (!dq.empty() && dq.back().v >= a[i])
        dq.pop_back();
    dq.push_back({a[i], i});  // dq.front().v为最小值

    // 按照题目要求输出最小值、最大值等
}

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing135 最大子序和
AcWing154 滑动窗口简单⭐⭐单调队列用数组模拟队列存储下标,求最小值时维护单调递增队列:先判断队首是否滑出窗口(q[hh] < i - k + 1hh++),再从队尾弹出所有大于等于当前元素的值(a[q[tt]] >= a[i]tt--),最后将当前下标入队,窗口形成后队首即为最小值;求最大值时同理维护单调递减队列(队尾弹出小于等于当前元素的值),两次遍历分别输出最小值和最大值,总时间复杂度 O ( n ) O(n) O(n)
AcWing1087 修剪草坪
AcWing1088 旅行问题
AcWing1089 烽火传递
AcWing1090 绿色通道
AcWing1091 理想的正方形
洛谷P1714 切蛋糕普及
洛谷P1886 单调队列普及
洛谷P11963 环线普及
AtCoderAT_awc0001_e Temperature Fluctuation Range
AtCoderAT_awc0012_e Stone Crossing Game
AtCoderAT_awc0033_e Minimum Cost of Stepping Stones⭐⭐⭐动态规划 + 单调队列优化dp[i] 为到达石头 i i i 的最小累计成本,转移方程为 dp[i] = min{dp[j]} + A[i](其中 i − K ≤ j < i i-K \leq j < i iKj<i),用单调递增队列维护滑动窗口 [ i − K , i − 1 ] [i-K, i-1] [iK,i1] 内的最小 dp 值:先弹出队首过期的元素(下标 < i-K),然后 dp[i] = 队首值 + A[i],再从队尾弹出所有值 >= dp[i] 的元素后入队,保证队首始终为窗口最小值,将转移复杂度从 O ( N × K ) O(N \times K) O(N×K) 优化到 O ( N ) O(N) O(N),最终输出 dp[N]
学而思编程[[训练计划]]提高进阶⭐⭐⭐⭐⭐单调队列优化 DP + 对数比较(避免高精度)将问题转化为:将 N N N 个数分成至多 K K K 段,每段内所有数相乘,求各段乘积之和的最大值;用 Node 结构体记录每个数字 1 ∼ 9 1\sim9 19 的出现次数,通过对数比较乘积大小(v1 += s[i] * ln[i])避免高精度;sum[i] 为前缀计数数组,d[i] 表示前 i − 1 i-1 i1 个数的最优划分;用单调队列维护窗口 [ i − K , i − 1 ] [i-K, i-1] [iK,i1] 内的最小值,保证任意连续 K K K 个数中至多选 K K K 题(即每段长度不超过 K K K);d[i] = sum[i-1] - sum[q[st]] + d[q[st]],队列中保持 d[q[j]] + sum[i] - sum[q[j]] 单调递增;最后根据 d[n+1] 中各数字出现次数计算答案乘积并对 10 9 + 7 10^9+7 109+7 取模。

【栈】

算法模板

手搓栈

// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[++tt] = x;
// 从栈顶弹出一个数
tt--;
// 栈顶的值
stk[tt];
// 判断栈是否为空,如果 tt > 0,则表示不为空
if (tt > 0) not empty
else empty

STL栈

stack<int> stk;
// 向栈顶插入一个数
stk.push(x);
// 栈顶的值
stk.top();
// 判断栈是否为空
stk.empty();

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing828 模拟栈简单栈(数组模拟)用数组 stk[N] 模拟栈,top 指针指向栈顶元素位置(初始为 0 0 0 表示空栈),push xstk[++top] = x(先自增再存值),poptop--(直接下移指针),empty 时判断 top == 0 输出 YES/NOquery 时输出 stk[top],四种操作均为 O ( 1 ) O(1) O(1),总时间复杂度 O ( M ) O(M) O(M)
AcWing[[2769 表达式]]
AcWing3302 表达式求值中等⭐⭐栈(双栈法)操作数栈 num运算符栈 op 分别存储数字和运算符,遍历表达式时遇到数字压入 num 栈,遇到左括号直接入 op 栈,遇到右括号则循环执行 eval()(弹出两个操作数和运算符计算后压回 num)直到遇到左括号,遇到运算符时若栈顶优先级 >= 当前则先 eval() 再入栈,最后清空 op 栈中剩余运算,num 栈顶即为结果;eval() 时注意先弹出的是右操作数 b,后弹出的是左操作数 a
AcWing6027 后缀表达式的值
AcWing6028 表达式括号匹配
AcWing6029 括弧匹配检验
AcWing6030 字符串匹配问题
AcWing6031 计算
AcWing6032 车厢调度
AcWing6033 中缀表达式值
洛谷AT_abc351_c Merge the balls普及-
洛谷AT_abc438_c 1D puyopuyo普及-
洛谷B2165 括号匹配普及-
洛谷B3614 栈普及-
洛谷B3758 括号序列普及-
洛谷P1165 日志分析普及-
洛谷P1981 表达式求值普及-
洛谷[[P3056 Clumsy Cows]]普及-
洛谷P10472 括号画家普及-
洛谷P1944 最长括号匹配普及
洛谷[[P1503 鬼子进村]]普及+
洛谷P3952 时间复杂度普及+

【集合】

算法模板

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷AT_abc366_c Balls and Bag Query普及-
洛谷AT_abc403_c 403 Forbidden普及-
洛谷AT_abc451_c Understory普及-
洛谷P3879 阅读理解普及-
洛谷AT_abc406_d Garbage Removal普及
洛谷P5250 木材仓库普及
洛谷P7912 小熊的果篮普及+
AtCoderAT_awc0013_b Investigation of Friend Relationships
AtCoderAT_awc0027_d Part-Time Job Shift Assignment

【并查集】

算法模板

朴素并查集+路径压缩

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所在的两个集合:
void merge(int a, int b)
{
	p[find(a)] = find(b);
}

维护size的并查集

int p[N], siz[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所在的两个集合:
void merge(int a, int b)
{
	if (find(a)==find(b)) return;
	size[find(b)] += size[find(a)];
	p[find(a)] = find(b);
}

带边权的并查集

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)] = d[y]-d[x]; // X和Y是同类
d[find(a)] = d[y]+1-d[x];  // X吃Y

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing[[237 程序自动分析]]中等⭐⭐⭐并查集 + 离散化先用 unordered_map 对变量编号(可能达 10 9 10^9 109)进行离散化映射为连续编号,然后两遍扫描:第一遍将所有 e=1(相等)约束用并查集合并,第二遍检查所有 e=0(不等)约束是否出现 find(x)==find(y) 的矛盾(即相等与不等冲突),若存在冲突输出 NO,否则输出 YES;注意必须先处理全部相等关系再检查不等关系,否则传递性会导致误判。
AcWing[[238 银河英雄传说]]简单⭐⭐⭐⭐带权并查集(距离向量)d[x] 记录战舰 x x x 到所在列队首的战舰间隔数,siz[x] 记录以 x x x 为根的列的战舰总数;find(x) 递归找根的同时用 d[x] += d[p[x]] 累加父节点到根的距离并路径压缩;M a b 合并时将 a a a 列接在 b b b 列尾部,设 d[pa] = siz[pb] a a a 列根到 b b b 列根的距离为 b b b 列大小),更新 siz[pb] += siz[pa] 后连边 p[pa] = pbC a b 时若同根则输出 abs(d[a]-d[b])-1(两舰之间的战舰数),不同根输出 `-1$。
AcWing[[239 奇偶游戏]]中等⭐⭐⭐⭐带权并查集(异或权值) + 离散化 + 前缀和转化将区间 [ l , r ] [l,r] [l,r] 中 1 的个数的奇偶性转化为前缀和异或关系 sum[r] ^ sum[l-1] = tt=0 为偶,t=1 为奇),用 unordered_map l − 1 l-1 l1 r r r 做离散化;find(x) 递归找根的同时用 d[x] ^= d[p[x]] 更新到根节点的异或权值并路径压缩,若 a a a b b b 同根则检查 d[a]^d[b] == t 是否成立,不同根则合并并设 d[pa] = d[a]^d[b]^t 保证新集合奇偶关系一致,遇到矛盾时输出前 i − 1 i-1 i1 个回答为最大自洽数。
AcWing240 食物链中等⭐⭐⭐⭐带权并查集(扩展域 / 距离向量)带权并查集维护每个节点到根节点的距离 d[x](模 3 意义下,0 表示同类,1 表示 x 被根吃,2 表示 x 吃根),find(x) 递归找根的同时累加路径距离并压缩;1 X Y(同类)时若同根则判 (d[x]-d[y])%3==0,不同根则合并并设 d[px]=d[y]-d[x] 使距离相等;2 X Y(X 吃 Y)时若同根则判 (d[x]-d[y]-1)%3==0(即 d[x]-d[y] ≡ 1 (mod 3)),不同根则合并并设 d[px]=d[y]+1-d[x];同时判 X>NY>NX==Y 且操作为 2 的情况为假话,累计假话数输出。
AcWing784 强盗团伙
AcWing836 合并集合简单并查集(路径压缩)p[N] 数组维护每个节点的父节点,初始化 p[i] = i 使每个节点自成一集合,M a b 合并时将 a 的根节点挂到 b 的根节点下(p[find(a)] = find(b)),Q a b 查询时判断 find(a) == find(b) 是否成立;find(x) 函数采用路径压缩优化,递归查找根节点的同时将路径上所有节点的父节点直接指向根节点,使后续查询接近 O ( 1 ) O(1) O(1),均摊时间复杂度 O ( α ( N ) ) O(\alpha(N)) O(α(N))
AcWing837 连通块中点的数量简单⭐⭐并查集(路径压缩 + 按大小合并)在基础并查集上增加 siz[N] 数组记录每个根节点所在集合的大小,初始化 siz[i] = 1C a b 合并时先判根是否相同,不同则按大小合并(将较小集合挂到较大集合下),更新 siz[find(b)] += siz[find(a)] 后再连边 p[find(a)] = find(b)Q1 直接比较根节点,Q2 输出 siz[find(a)]find(x) 采用路径压缩,保证均摊时间复杂度 O ( α ( N ) ) O(\alpha(N)) O(α(N))
AcWing1249 亲戚
AcWing1250 格子游戏
AcWing1251 打击犯罪
AcWing1252 搭配购买
AcWing1253 家谱
AcWing6058 亲戚
洛谷[[P2978 Tea Time]]普及-
洛谷P14077 连通图普及-
洛谷AT_abc399_c Make it Forest普及
洛谷AT_abc420_e Reachability Query普及
洛谷[[P1546 Agri-Net]]普及
洛谷P1551 亲戚普及
洛谷P1621 集合普及
洛谷[[P1661 扩散]]普及
洛谷[[P3367 并查集]]普及
洛谷AT_abc451_f Make Bipartite 3普及+
洛谷P1196 银河英雄传说普及+
洛谷[[P1197 星球大战]]普及+
洛谷P1525 关押罪犯普及+
洛谷P1892 团伙普及+
洛谷P1955 程序自动分析普及+
洛谷P2024 食物链普及+
洛谷[[P2294 狡猾的商人]]普及+
洛谷[[P3535 TOU-Tour de Byteotia]]普及+
洛谷P5689 多叉堆普及+
洛谷P9869 三值逻辑普及+
AtCoderAT_awc0080_e Paint Drop⭐⭐⭐⭐并查集(按行维护 + 路径压缩)对每行独立维护并查集 nxt[i][j] 表示第 i i i 行中位置 j j j下一个未涂色位置,初始化 nxt[i][j] = j;每次查询时遍历菱形区域内的每一行,计算该行的列区间 [ j L , j R ] [j_L, j_R] [jL,jR],用 find(i, j_L) 找到第一个未涂色位置,累加分数后将该位置指向 find(i, j+1)(标记已涂色),再继续 find 查找下一个未涂色位置直到超出 j R j_R jRfind 函数带路径压缩,保证每个格子最多被访问一次,总时间复杂度近似 O ( H × W × α ( W ) + Q × D m a x × α ( W ) ) O(H \times W \times \alpha(W) + Q \times D_{max} \times \alpha(W)) O(H×W×α(W)+Q×Dmax×α(W))
其他[[道路]]
其他[[连通]]
其他[[朋友]]

【线段树】

算法模板

区修+区查(求最值)

// 求最小值,如果要求最大值,把mn改为mx
int n, m;
int w[N];
struct Node
{
    int l, r;
    int dt, mn;  // 求最小值,dt是懒标记

}tr[N*4];

void pushup(int u)  // 由子节点的信息,来计算父节点的信息
{
    tr[u].mn = min(tr[u<<1].mn, tr[u<<1|1].mn);  // 求最小值
}
void pushdown(int u)
{
    auto &root = tr[u], &l = tr[u<<1], &r = tr[u<<1|1];
    l.dt += root.dt, l.mn += root.dt;
    r.dt += root.dt, r.mn += root.dt;
    root.dt = 0;
}
void build(int u, int l, int r)
{
    if (l==r) tr[u] = {l, r, 0, w[l]};
    else
    {
        tr[u] = {l, r};
        int mid = l+r >> 1;
        build (u<<1, l, mid), build(u<<1|1, mid+1, r);
        pushup(u);
    }
}
void update(int u, int l, int r, int d)
{
    if (tr[u].l>=l && tr[u].r<=r)
    {
        tr[u].dt += d, tr[u].mn += d;  // 修改值
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l<=mid) update(u<<1, l, r, d);
        if (r>mid) update(u<<1|1, l, r, d);
        pushup(u);
    }
}
int query(int u, int l, int r)
{
    if (tr[u].l>=l && tr[u].r<=r)  // 树中节点,已经被完全包含在[l,r]中了
    {
        return tr[u].mn;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        int res = INF;  // 设最大值
        if (l<=mid) res = query(u<<1, l, r);
        if (r>mid) res = min(res, query(u<<1|1, l, r));  // 求最小值
        return res;
    }
}

区修+区查(求和)

int n, m;
int w[N];
struct Node
{
    int l, r;
    int dt, sum;  // 求和,dt是懒标记
}tr[N*4];

void pushup(int u)  // 由子节点的信息,来计算父节点的信息
{
    tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum
}
void pushdown(int u)
{
    auto &root = tr[u], &l = tr[u<<1], &r = tr[u<<1|1];
    l.dt += root.dt;
    r.dt += root.dt;
    // 如果要求区间和
    l.sum += root.dt * (l.r - l.l + 1);
    r.sum += root.dt * (r.r - r.l + 1);
    root.dt = 0;
}
void build(int u, int l, int r)
{
    if (l==r) tr[u] = {l, r, 0, w[l]};
    else
    {
        tr[u] = {l, r};
        int mid = l+r >> 1;
        build (u<<1, l, mid), build(u<<1|1, mid+1, r);
        pushup(u);
    }
}
void update(int u, int l, int r, int d)
{
    if (tr[u].l>=l && tr[u].r<=r)
    {
        tr[u].dt += d;
        // 如果要求区间和
        tr[u].sum += d * (tr[u].r - tr[u].l + 1)
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l<=mid) update(u<<1, l, r, d);
        if (r>mid) update(u<<1|1, l, r, d);
        pushup(u);
    }
}
int query(int u, int l, int r)
{
    if (tr[u].l>=l && tr[u].r<=r)  // 树中节点,已经被完全包含在[l,r]中了
    {
        // 如果要求区间和
        return tr[u].sum;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        int res = INF;
        // 如果要求区间和
        if (l<=mid) res += query(u<<1, l, r);
        if (r>mid) res += query(u<<1|1, l, r);
        return res;
    }
}

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing[[245 你能回答这些问题吗]]中等⭐⭐⭐⭐线段树(最大子段和)线段树每个节点维护四个值:sum(区间总和)、pre(最大前缀和)、suf(最大后缀和)、best(最大连续子段和);pushupbest = max(左.best,右.best,左.suf + 右.pre) 处理三种情况(完全在左、完全在右、跨过中点);1 x y 查询时若区间横跨左右子树则合并左右 Node 结果并返回完整结构体,2 x y 修改时定位到叶子替换值后自底向上 pushup;注意查询时 x>y 需交换,单次操作 O ( log ⁡ N ) O(\log N) O(logN)
AcWing[[246 区间最大公约数]]困难⭐⭐⭐⭐线段树 + 树状数组 + 差分数组利用数学性质 gcd ⁡ ( A [ l . . r ] ) = gcd ⁡ ( A [ l ] , gcd ⁡ ( b [ l + 1.. r ] ) ) \gcd(A[l..r]) = \gcd(A[l], \gcd(b[l+1..r])) gcd(A[l..r])=gcd(A[l],gcd(b[l+1..r]))(其中 b [ i ] = A [ i ] − A [ i − 1 ] b[i]=A[i]-A[i-1] b[i]=A[i]A[i1] 为差分数组),将区间加转化为差分数组的单点修改;用树状数组维护差分数组前缀和以 O ( log ⁡ N ) O(\log N) O(logN) 查询 A [ l ] A[l] A[l],用线段树维护差分数组的区间 GCD 以 O ( log ⁡ N ) O(\log N) O(logN) 查询 gcd ⁡ ( b [ l + 1.. r ] ) \gcd(b[l+1..r]) gcd(b[l+1..r])C l r d 时树状数组 add(l,d)add(r+1,-d),线段树 update(l,d)update(r+1,-d)Q l r 时输出 abs(gcd(sum(l), query(l+1,r))) l = r l=r l=r 时直接输出 abs(A[l])),单次操作 O ( log ⁡ N ) O(\log N) O(logN)
AcWing[[247 亚特兰蒂斯]]困难⭐⭐⭐⭐⭐扫描线 + 线段树 + 离散化将每个矩形拆分为入边 x = x 1 , k = + 1 x=x_1, k=+1 x=x1,k=+1)和出边 x = x 2 , k = − 1 x=x_2, k=-1 x=x2,k=1)两条扫描线,对所有 y y y 坐标离散化后建立线段树维护 y y y 方向覆盖长度 len 和覆盖次数 cntpushup 时若 cnt>0len 为区间实际长度,否则继承子节点之和;按 x x x 排序扫描线后从左到右遍历,每次用 tr[1].len * (seg[i].x - seg[i-1].x) 累加条带面积,再用 update 将当前边的 y y y 范围加入或移除覆盖(右端点用 find(y2)-1 因为节点 i i i 代表区间 [ a l l s [ i ] , a l l s [ i + 1 ] ] [alls[i], alls[i+1]] [alls[i],alls[i+1]]),最终得到所有矩形面积并。
AcWing1275 最大数中等⭐⭐⭐线段树(单点修改 + 区间查询)线段树维护动态序列的区间最大值,建树时覆盖 [ 1 , m ] [1, m] [1,m](最大可能长度),A t 添加操作时在位置 n+1 做单点修改 update(1, n+1, n+1, (last+t)%p)n++Q L 询问操作时查询区间 [n-L+1, n] 的最大值并更新 last 为查询结果(供下次添加操作使用);线段树每个节点存区间最大值,pushup 时取左右子树最大值,query 时合并跨越中点的左右区间结果,单次操作 O ( log ⁡ m ) O(\log m) O(logm),总时间复杂度 O ( m log ⁡ m ) O(m \log m) O(mlogm)
洛谷AT_abc340_e Mancala 2普及+
洛谷[[P1198 最大数]]普及+
洛谷P3372 线段树1普及+
洛谷AT_abc360_f InterSections提高+
洛谷[[P3114 Stampede]]提高+
洛谷P1600 天天爱跑步省选
AtCoderAT_awc0005_e Mountain Height Survey
AtCoderAT_awc0009_e Temperature Fluctuation Survey
AtCoderAT_awc0014_e Flowerbed Watering Management
AtCoderAT_awc0020_e Shelving Books on a Bookshelf⭐⭐⭐线段树(区间最大值 + 在线二分查找)用线段树维护每个书架位置的最大承重能力find 函数优先在左子树二分查找第一个容量 >= W[i] 的位置,若当前区间最大值 < W[i] 则剪枝返回 -1;找到位置后 update 将其设为 -INF 标记已占用,成功放置则计数器加一;按书顺序依次处理,总时间复杂度 O ( ( N + M ) log ⁡ M ) O((N+M) \log M) O((N+M)logM)
AtCoderAT_awc0062_e Radio Tower and Signal Strength⭐⭐⭐⭐差分 + 前缀和 + 线段树(区间最值)每个信号源 ( X i , B i ) (X_i, B_i) (Xi,Bi) 的贡献是三角形函数(中心 X i X_i Xi 处为 B i B_i Bi,向两侧线性递减到 0),用差分数组记录斜率变化:在 [ X i − B i + 1 , X i ] [X_i-B_i+1, X_i] [XiBi+1,Xi] 区间斜率 +1,在 [ X i + 1 , X i + B i ] [X_i+1, X_i+B_i] [Xi+1,Xi+Bi] 区间斜率 -1,即 diff[x-b+1] += 1diff[x+1] -= 2diff[x+b+1] += 1;对差分数组做两次前缀和(斜率→信号强度)得到每个整数坐标 p p p f ( p ) f(p) f(p) 值;最后用线段树维护区间最大值,每次查询 [ L j , R j ] [L_j, R_j] [Lj,Rj] 的区间最大值并输出,坐标用 OFFSET=200000 偏移处理负数,总时间复杂度 O ( N + M + Q log ⁡ M ) O(N + M + Q \log M) O(N+M+QlogM) M = 400000 M=400000 M=400000 为坐标范围)。
AtCoderAT_awc0063_e Number of Blocks in an Interval⭐⭐⭐⭐线段树(区间赋值 + 懒标记)线段树每个节点维护区间左端点颜色 lc右端点颜色 rc块数 cnt懒标记 dt-1 表示无标记);pushup 时合并左右子节点块数,若 左子.rc == 右子.lccnt--(边界同色合并);pushdown 时将懒标记下传,子节点区间变为同色且 cnt=1update 区间赋值时直接设置懒标记并令 lc=rc=dcnt=1query 时若横跨左右子树则合并结果并处理边界同色情况,单次操作 O ( log ⁡ N ) O(\log N) O(logN)
AtCoderAT_awc0081_e Balanced Groups in Tournament Partition⭐⭐⭐⭐⭐线段树 + 滑动窗口 + 完全二叉树性质递归二分分组天然形成完全二叉树,线段树节点值维护区间内 1 的数量(白帽人数),val 从 1 开始每层翻倍表示该层组应含叶子数,若 tr[i] == val/2 则该组为平衡组,用 acc 统计当前平衡组数量;滑动窗口枚举操作区间 [ l , l + K − 1 ] [l, l+K-1] [l,l+K1],将区间内 0 移到左、1 移到右等价于把窗口内 cnt1 集中到右侧,通过 update 单点修改并自底向上更新 tracc(跨越平衡条件时增减计数),每次窗口移动后 res = max(res, acc),最终输出最大平衡组数;时间复杂度 O ( 2 N × N ) O(2^N \times N) O(2N×N)
AtCoderAT_awc0082_e Company Organization and Salaries⭐⭐⭐⭐⭐DFS 序 + 线段树套树状数组(树套树) + 离散化先通过 DFS 求欧拉序 将子树转化为连续区间 [ I n [ v ] , O u t [ v ] ] [In[v], Out[v]] [In[v],Out[v]]离线收集所有初始工资和修改操作中的新工资,对每个线段树节点单独离散化值域后建立树状数组1 v x 修改时在 In[v] 位置删除旧工资、加入新工资,2 v 查询时在线段树区间 [ I n [ v ] , O u t [ v ] ] [In[v], Out[v]] [In[v],Out[v]] 中统计值域 [ A [ v ] + 1 , I N F ] [A[v]+1, INF] [A[v]+1,INF] 的个数并输出;外层线段树维护 DFS 序区间,内层树状数组维护值域频次,单次操作 O ( log ⁡ 2 N ) O(\log^2 N) O(log2N)
AtCoderAT_awc0087_e Change of Assigned Interval⭐⭐⭐⭐线段树(最大子段和) + 前缀和A 类任务设为 + P i +P_i +PiT 类任务设为 − P i -P_i Pi 得到数组 v v v,用前缀和 sa 统计不翻转时青木的基础成本;翻转区间 [ l , r ] [l,r] [l,r] 的净效果等价于基础值减去该区间内的最大子段和(因为翻转后原 A 成本移除、原 T 成本加入),线段树每个节点维护 sum(区间和)、pre(最大前缀和)、suf(最大后缀和)、best(最大子段和),pushupbest = max(左.best,右.best,左.suf + 右.pre);查询时 base = sa[R]-sa[L-1]maxReduce = max(0, query(L,R).best),输出 base - maxReduce,单次查询 O ( log ⁡ N ) O(\log N) O(logN)

【树状数组】

算法模板

int lowbit(int x)  //提出x的低位2次幂数
{
    return x & -x;
}
void add(int x, int c)  //向后修
{
    for (int i=x; i<=n; i+=lowbit(i)) tr[i] += c;
}
int query(int x)  //向前查
{
    int res = 0;
    for (int i=x; i; i-=lowbit(i)) res += tr[i];
    return res;
}

// 初始化树状数组
for (int i = 1; i <= n; i++)
    cin >> a[i];

// 初始化树状数组
// 将原始数组转换为差分数组存储到树状数组中
for (int i = 1; i <= n; i++)
    add(i, a[i] - a[i - 1]);  // 存储差分值

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing241 楼兰图腾简单⭐⭐⭐树状数组(前缀和统计)从左到右扫描,用树状数组维护已出现元素的集合,query(n)-query(y) 得到左边比 a [ i ] a[i] a[i] 大的元素个数记为 Greater[i]query(y-1) 得到左边比 a [ i ] a[i] a[i] 小的元素个数记为 Lower[i];清空树状数组后从右到左扫描,用 Greater[i] * (query(n)-query(y)) 累加 V 图腾数(中间点 j j j 左边有 Greater[i] 个比它大的,右边有 query(n)-query(y) 个比它大的),用 Lower[i] * query(y-1) 累加 图腾数(中间点 j j j 左边有 Lower[i] 个比它小的,右边有 query(y-1) 个比它小的),每次扫描后 add(y,1) 将当前元素加入树状数组,总时间复杂度 O ( N log ⁡ N ) O(N \log N) O(NlogN)
AcWing242 一个简单的整数问题
AcWing243 一个简单的整数问题2
AcWing244 谜一样的牛
洛谷P1996 约瑟夫问题普及-
洛谷P3184 Counting Haybales普及-
洛谷P10497 Lost Cows普及-
洛谷AT_abc353_c Sigma Problem普及
洛谷AT_abc441_e A B substring普及
洛谷[[B3874 小杨的握手问题]]普及
洛谷P4378 Out of Sorts普及
洛谷P3368 树状数组2普及+
洛谷P3374 树状数组1普及+
洛谷AT_abc360_g Suitable Edit for LIS提高+
洛谷P3605 Promotion Counting提高+
洛谷P5677 配对统计提高+
AtCoderAT_awc0006_e Store Sales Management
AtCoderAT_awc0008_e Organizing the Bookshelf
AtCoderAT_awc0015_e Count the Types of Flowers⭐⭐⭐树状数组 + 离线查询(按右端点排序)将查询按右端点升序排序,从左到右扫描数组,用 last[x] 记录物种 x x x最后出现位置,树状数组维护"当前扫描范围内每个物种最右出现位置"的标记(值为 1);当物种 x x x 在位置 i i i 出现时,若 last[x] != 0 则先在旧位置 add(last[x], -1) 取消标记,再在当前位置 add(i, 1) 添加新标记,更新 last[x] = i;回答查询时 ans[id] = query(r) - query(l-1) 统计区间 [ l , r ] [l,r] [l,r] 内有效标记数(即不同物种数),最后按原始编号输出答案,总时间复杂度 O ( ( N + Q ) log ⁡ N ) O((N+Q) \log N) O((N+Q)logN)
AtCoderAT_awc0025_e Organizing the Bookshelf⭐⭐⭐树状数组 + 二分查找(第 k 小 / 顺序统计)树状数组维护每个位置是否存在书( 1 1 1 表示存在, 0 0 0 表示已移除),query(x) 返回前 x x x 个位置中书的数量;find(k) 通过二分查找定位第 k k k 本书的实际位置(找最小的 pos 使得 query(pos) >= k);每次操作若 T j > t n T_j > tn Tj>tn(当前剩余书数)则直接输出 tn,否则找到实际位置 pos 后将 d[pos]--,若耐久度降为 0 0 0add(pos,-1) 标记移除并 tn--,最后输出 tn;每次操作 O ( log ⁡ 2 N ) O(\log^2 N) O(log2N)
AtCoderAT_awc0027_e Selection of Contiguous Intervals⭐⭐⭐⭐树状数组 + 离散化将条件转化为前缀和形式:定义 s a [ i ] = ∑ j = 1 i ( A j + M ) sa[i] = \sum_{j=1}^{i}(A_j + M) sa[i]=j=1i(Aj+M),则 f ( l , r ) ≤ K f(l,r) \leq K f(l,r)K 等价于 s a [ l − 1 ] ≥ s a [ r ] − K sa[l-1] \geq sa[r] - K sa[l1]sa[r]K;收集所有 s a [ i ] sa[i] sa[i] s a [ i ] − K sa[i]-K sa[i]K 离散化后,遍历右端点 r r r,用树状数组查询已插入的 s a [ l − 1 ] sa[l-1] sa[l1] 中大于等于 s a [ r ] − K sa[r]-K sa[r]K 的数量(总数减前缀和),再将 s a [ r ] sa[r] sa[r] 插入树状数组,时间复杂度 O ( N log ⁡ N ) O(N \log N) O(NlogN)
AtCoderAT_awc0032_e Multiple Bonus⭐⭐⭐⭐树状数组 + 分块优化(阈值分治) B = N B = \sqrt{N} B=N 为阈值, k k k k ≤ B k \leq B kBtag[k] 标记累计增量,查询时通过 tag[k] * (x/k) 快速计算前 x x x 个位置中 k k k 的倍数个数得到总贡献; k k k k > B k > B k>B&#x76F4;接遍历 i = k , 2 k , 3 k , … ≤ N i=k,2k,3k,\ldots \leq N i=k,2k,3k,N 用树状数组 add(i,v) 单点修改,查询时 query(x) 累加;最终答案为 sa[x] + query(x) + Σ(tag[k]*(x/k)),修改操作小 k k k O ( 1 ) O(1) O(1)、大 k k k O ( N / k ) ≤ O ( N ) O(N/k) \leq O(\sqrt{N}) O(N/k)O(N ),查询为 O ( N + log ⁡ N ) O(\sqrt{N}+\log N) O(N +logN)
AtCoderAT_awc0047_c Piggy Bank Management⭐⭐树状数组(差分思想)用树状数组维护差分数组 d[i] = A[i] - A[i-1],初始化时 add(i, A[i]-A[i-1])1 L R X 区间修改时执行 add(L, X)add(R+1, -X),将区间加转化为两次单点修改;2 P 单点查询时输出 query(P)(差分数组前缀和即为原数组值);利用差分思想使区间修改和单点查询均为 O ( log ⁡ N ) O(\log N) O(logN)

【单调栈】

算法模板

// 常见模型:找出每个数左边离它最近的比它大/小的数
// 输入数组元素
for (int i = 1; i <= n; i++)
    cin >> a[i];

// 使用单调栈处理每个元素
for (int i = 1; i <= n; i++)
{
    // 维护单调递增栈:弹出所有大于等于当前元素的栈顶元素
    while (tt && a[stk[tt]] >= a[i])
        tt--;

    // 如果栈不为空,当前元素左边第一个比它小的元素就是栈顶元素
    if (tt)
        ans[i] = a[stk[tt]];
    else
        ans[i] = -1;  // 栈为空表示左边没有更小的元素

    // 将当前元素的下标压入栈
    stk[++tt] = i;
}

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing830 单调栈简单⭐⭐单调栈(维护递增栈)从左到右扫描数组,用单调递增栈维护可能成为"左边第一个更小元素"的候选值,栈中存储下标;对于每个新元素 x x x,先 while (tt && a[stk[tt]] >= x) tt-- 弹出所有大于等于 x x x 的栈顶元素(这些元素不可能成为后续元素的左边第一个更小值),若栈非空则 ans[i] = a[stk[tt]](栈顶即为左边第一个比它小的数),否则输出 -1,最后将当前下标 i 压入栈;每个元素最多入栈出栈一次,时间复杂度 O ( N ) O(N) O(N)
洛谷AT_abc372_d Buildings普及
洛谷[[P1323 删数问题]]普及
洛谷P1901 发射站普及
洛谷[[P5788 单调栈]]普及
洛谷[[P8082 KEKS]]普及
AtCoderAT_awc0045_e Interval Evaluation Value⭐⭐⭐⭐⭐单调栈 + 线段树(区间最值)先用单调递增栈从左到右求每个元素左边第一个小于等于它的位置 L [ i ] L[i] L[i],再用单调递减栈从右到左求右边第一个大于它的位置 R [ i ] R[i] R[i],确定以 A [ i ] A[i] A[i] 为唯一最小值的合法区间范围;线段树叶子节点存储从位置 l l l 开始、长度为 W W W 的区间和 sa[l+W-1]-sa[l-1];枚举每个位置 i i i 作为最小值,计算合法起始位置范围 left = max(L[i]+1, i-W+1)right = min(i, R[i]-W),若 left <= rightquery(left,right) 得到最大区间和,更新答案 ans = max(ans, maxsum + K*A[i]),总时间复杂度 O ( N log ⁡ N ) O(N \log N) O(NlogN)

【哈希表】

算法模板

拉链法

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

开放寻址法

int h[N], null=0x3f3f3f3f;
// 如果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;
}

算法应用

题目来源标题难度星级考察算法一句话思路总结
AcWing840 模拟散列表简单⭐⭐哈希表(拉链法 / 开放寻址法)拉链法:用数组 h[k] 存哈希值为 k k k 的链表头指针,e[idx] 存值,ne[idx] 存下一个节点下标,insert 时头插法将新节点挂到链表头部,find 时遍历对应链表查找;开放寻址法:数组 h[N] 直接存值,null=0x3f3f3f3f 表示空位,find(x) 通过 (x%N+N)%N 计算哈希值后线性探测(冲突时 k++ 循环到数组开头),返回 x x x 所在位置或第一个空位,I x 时直接赋值,Q x 时判断 h[k] != null;两种方法哈希函数均为 (x%N+N)%N 处理负数,时间复杂度均摊 O ( 1 ) O(1) O(1)
洛谷P1918 保龄球普及-
洛谷P3405 Cities and States普及-
洛谷P4305 不重复数字普及-
洛谷P15799 找数普及-
洛谷P2814 家谱普及
洛谷P5266 学籍管理普及
洛谷P2852 Milk Patterns普及+
洛谷P8819 星战省选
AtCoderAT_awc0036_b Managing the Guest List
AtCoderAT_awc0088_d Control Panel Operation Sequence⭐⭐⭐⭐全排列枚举 + 哈希表计数(状态模拟)利用 N ≤ 9 N \leq 9 N9 的小数据范围,用 next_permutation 枚举所有 N ! N! N! 种操作顺序,对每个排列模拟灯状态变化:按顺序验证操作 i i i 的执行条件( A i A_i Ai 要求为 1 1 1 的位置灯必须亮、 B i B_i Bi 要求为 1 1 1 的位置灯必须灭),满足后按 X i X_i Xi 翻转对应灯并统计亮灯数存入向量 v v v;用 map<vector<int>, int> 记录每种亮灯数量序列的出现次数;查询时直接更新 C T q = Y q C_{T_q}=Y_q CTq=Yq 后输出 mp[query];预处理检查是否存在 A i [ j ] = B i [ j ] = ′ 1 ′ A_i[j]=B_i[j]='1' Ai[j]=Bi[j]=1 的矛盾操作,存在则所有答案为 0 0 0

【平衡树】

算法模板

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷[[P3369 普通平衡树]]提高+
洛谷[[P3391 文艺平衡树]]提高+
洛谷[[P3835 可持久化平衡树]]省选

【动态树】

算法模板

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷[[P3690 动态树(LCT)]]省选

【主席树】

算法模板

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷P3834 可持久化线段树2提高+
洛谷[[P3919 可持久化线段树1]]提高+

【树套树】

算法模板

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷P3380 树套树省选

【笛卡尔树】

算法模板

struct DescarTree
{
	int ls, rs;
}t[N];  //笛卡尔树
int stk[N], top;  //stk[]单调递增栈,存放元素下标,增栈指权值,栈顶元素权值大

for (int i=1; i<=n; i++)
{
	cin >> p[i];
	int pos = top;  //top栈原栈顶,pos栈当前操作位置
	while (p[stk[pos]]>p[i]) pos--;  //出栈,stk[pos]存放下标,元素权值单调递增栈
	if (pos<top) t[i].ls = stk[pos+1];  //最后出栈stk[pos+1]为当前树节点t[i]左儿子
	if (pos) t[stk[pos]].rs = i;  //将入栈,原栈顶为父,当前(元素下标)为右儿子
	stk[++pos] = i;  //入栈,当前元素下标i入栈
	top = pos;  // 新栈顶
}

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷P5854 笛卡尔树普及+⭐⭐⭐笛卡尔树(单调栈构建)笛卡尔树同时满足二叉搜索树性质(节点编号中序遍历为 1 ∼ n 1\sim n 1n)和小根堆性质(权值 p [ i ] p[i] p[i] 父节点小于子节点);用单调递增栈(栈顶元素权值最大)维护当前右链,对于新元素 i i i:从栈顶弹出所有权值 > p [ i ] > p[i] >p[i] 的元素,若存在出栈元素则最后出栈的元素成为 i i i左儿子t[i].ls = stk[pos+1]),若栈非空则 i i i 成为栈顶元素的右儿子t[stk[pos]].rs = i),然后将 i i i 入栈;每个节点最多入栈出栈一次,时间复杂度 O ( N ) O(N) O(N)

【点分治】

算法模板

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷[[P3806 点分治1]]提高+

【莫队】

算法模板

// 询问结构体
struct Q
{
    int l, r;           // 区间左右端点
    int id;             // 询问编号
} q[N];

// 莫队排序比较函数(奇偶性优化)
bool cmp(Q a, Q b)
{
    if (a.l / B != b.l / B)
        return a.l < b.l;           // 不同块按左端点升序
    return a.r < b.r;               // 同块按右端点升序
}

// 添加一个数到当前区间
void add(int x)
{
    sum -= cnt[x] * cnt[x];         // 减去旧贡献
    cnt[x]++;                       // 增加计数
    sum += cnt[x] * cnt[x];         // 加上新贡献
}

// 从当前区间删除一个数
void del(int x)
{
    sum -= cnt[x] * cnt[x];         // 减去旧贡献
    cnt[x]--;                       // 减少计数
    sum += cnt[x] * cnt[x];         // 加上新贡献
}

// 计算分块大小
B = sqrt(n);

// 按照莫队顺序排序询问
sort(q + 1, q + m + 1, cmp);

// 初始化当前区间为空,l=1, r=0表示空区间
for (int i = 1, l = 1, r = 0; i <= m; i++)
{
	// 移动指针到目标区间
	while (l > q[i].l)          // 左边界向左扩展
		add(a[--l]);
	while (r < q[i].r)          // 右边界向右扩展
		add(a[++r]);
	while (l < q[i].l)          // 左边界向右收缩
		del(a[l++]);
	while (r > q[i].r)          // 右边界向左收缩
		del(a[r--]);

	// 存储当前区间的答案
	ans[q[i].id] = sum;
}
for (int i=1; i<=m; i++)
	cout << ans[i] << endl;

算法应用

题目来源标题难度星级考察算法一句话思路总结
洛谷P2709 小 B 的询问提高+⭐⭐⭐莫队算法(分块 + 双指针 + 奇偶性优化)将询问按左端点所在块升序排序,同一块内按右端点升序排序(奇偶性优化可进一步减少指针移动);用双指针 l , r l,r l,r 维护当前区间,通过 add(x)sum -= cnt[x]^2; cnt[x]++; sum += cnt[x]^2)和 del(x)sum -= cnt[x]^2; cnt[x]--; sum += cnt[x]^2)在 O ( 1 ) O(1) O(1) 时间内增量更新答案;每次移动指针后将 sum 存入对应询问编号的位置,最后按原始顺序输出;分块大小 B = n B = \sqrt{n} B=n ,总时间复杂度 O ( ( n + m ) n ) O((n+m)\sqrt{n}) O((n+m)n )
AtCoderAT_awc0088_e Intervals That Can Be Arranged Alternately⭐⭐⭐⭐莫队算法 + 前缀差分转化定义前缀差分数组 cumul [ i ] \text{cumul}[i] cumul[i] 为前 i i i 个石头中白减黑的数量,区间 [ l , r ] [l,r] [l,r] 能交替排列(好区间)当且仅当 $\
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值