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

【队列】

算法模板

手搓队列

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

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
AcWing 829 模拟队列 简单 队列(数组模拟) 用数组 q[N] 模拟队列,维护队首指针 hh(初始为 \(0\))和队尾指针 tt(初始为 \(-1\)),pushq[++tt]=xpophh++query 输出 q[hh]empty 判断 hh<=tt 是否成立。
AcWing 6034 周末舞会
AcWing 6035 Blah数集
AcWing 6036 围圈报数
洛谷 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 宝石项链 普及+
学而思编程 约瑟夫游戏(三) 普及奠基 ⭐⭐ 队列 用队列模拟围圈报数,每次将前 \(a_i \bmod \text{size}\) 个人移到队尾,队首即为淘汰者。

【链表】

算法模板

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

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
AcWing 826 单链表 简单 链表(数组模拟) 用数组 e[N] 存储节点值、ne[N] 存储下一个节点下标,维护头指针 head(初始 \(-1\))和可用节点索引 idx(初始 \(0\)),H x 时头插(e[idx]=x, ne[idx]=head, head=idx++),I k x 时在"第 \(k\) 个插入的节点"后插入(e[idx]=x, ne[idx]=ne[k-1], ne[k-1]=idx++),D k 时删除其后节点(k=0 删头结点,否则 ne[k-1]=ne[ne[k-1]]),最后从头指针遍历输出。
AcWing 827 双链表 简单 双链表(数组模拟) 01 作为左右哨兵节点,e[idx] 存值、l[idx]r[idx] 分别存左右指针,插入时在节点 \(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\) 左侧插入(即 \(l[k]\) 右侧),D k 直接删除第 \(k\) 个节点,最后从 r[0] 遍历到哨兵 \(1\) 输出。
AcWing 4089 小熊的果篮
洛谷 [[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为最小值

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

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
AcWing 135 最大子序和
AcWing 154 滑动窗口 简单 ⭐⭐ 单调队列 用数组模拟队列存储下标,求最小值时维护单调递增队列:先判断队首是否滑出窗口(q[hh] < i - k + 1hh++),再从队尾弹出所有大于等于当前元素的值(a[q[tt]] >= a[i]tt--),最后将当前下标入队,窗口形成后队首即为最小值;求最大值时同理维护单调递减队列(队尾弹出小于等于当前元素的值),两次遍历分别输出最小值和最大值,总时间复杂度 \(O(n)\)
AcWing 1087 修剪草坪
AcWing 1088 旅行问题
AcWing 1089 烽火传递
AcWing 1090 绿色通道
AcWing 1091 理想的正方形
洛谷 P1714 切蛋糕 普及
洛谷 P1886 单调队列 普及
洛谷 P11963 环线 普及
AtCoder AT_awc0001_e Temperature Fluctuation Range
AtCoder AT_awc0012_e Stone Crossing Game
AtCoder AT_awc0033_e Minimum Cost of Stepping Stones ⭐⭐⭐ 动态规划 + 单调队列优化 dp[i] 为到达石头 \(i\) 的最小累计成本,转移方程为 dp[i] = min{dp[j]} + A[i](其中 \(i-K \leq j < i\)),用单调递增队列维护滑动窗口 \([i-K, i-1]\) 内的最小 dp 值:先弹出队首过期的元素(下标 < i-K),然后 dp[i] = 队首值 + A[i],再从队尾弹出所有值 >= dp[i] 的元素后入队,保证队首始终为窗口最小值,将转移复杂度从 \(O(N \times K)\) 优化到 \(O(N)\),最终输出 dp[N]
AtCoder AT_awc0101_e Choosing Flowerbed Intervals ⭐⭐⭐⭐ 双指针 + 单调队列 + 哈希表 用双指针维护满足两个条件的最大窗口:右指针 \(j\) 从左到右枚举,用单调递增/递减队列分别维护区间 \([i,j]\) 的最小值和最大值(保证条件2),用 map 维护区间内不同品种的数量 \(D\)(保证条件1),当 \(D \times (j-i+1) > K\)\(\max - \min > M\) 时收缩左指针 \(i\) 直到满足条件,以 \(j\) 为右端点的合法区间数为 \((j-i+1)\),累加得到答案,时间复杂度 \(O(N \log N)\)map 操作),空间复杂度 \(O(N)\)
学而思编程 训练计划 提高进阶 ⭐⭐⭐⭐⭐ 单调队列优化 DP + 对数比较(避免高精度) 将问题转化为:将 \(N\) 个数分成至多 \(K\) 段,每段内所有数相乘,求各段乘积之和的最大值;用 Node 结构体记录每个数字 \(1\sim9\) 的出现次数,通过对数比较乘积大小(v1 += s[i] * ln[i])避免高精度;sum[i] 为前缀计数数组,d[i] 表示前 \(i-1\) 个数的最优划分;用单调队列维护窗口 \([i-K, i-1]\) 内的最小值,保证任意连续 \(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\) 取模。

【栈】

算法模板

手搓栈

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

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
AcWing 828 模拟栈 简单 栈(数组模拟) 用数组 stk[N] 模拟栈,top 指针指向栈顶元素位置(初始为 \(0\) 表示空栈),push xstk[++top] = x(先自增再存值),poptop--(直接下移指针),empty 时判断 top == 0 输出 YES/NOquery 时输出 stk[top],四种操作均为 \(O(1)\),总时间复杂度 \(O(M)\)
AcWing [[2769 表达式]]
AcWing 3302 表达式求值 中等 ⭐⭐ 栈(双栈法) 操作数栈 num运算符栈 op 分别存储数字和运算符,遍历表达式时遇到数字压入 num 栈,遇到左括号直接入 op 栈,遇到右括号则循环执行 eval()(弹出两个操作数和运算符计算后压回 num)直到遇到左括号,遇到运算符时若栈顶优先级 >= 当前则先 eval() 再入栈,最后清空 op 栈中剩余运算,num 栈顶即为结果;eval() 时注意先弹出的是右操作数 b,后弹出的是左操作数 a
AcWing 6027 后缀表达式的值
AcWing 6028 表达式括号匹配
AcWing 6029 括弧匹配检验
AcWing 6030 字符串匹配问题
AcWing 6031 计算
AcWing 6032 车厢调度
AcWing 6033 中缀表达式值
洛谷 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 小熊的果篮 普及+
AtCoder AT_awc0013_b Investigation of Friend Relationships
AtCoder AT_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\))进行离散化映射为连续编号,然后两遍扫描:第一遍将所有 e=1(相等)约束用并查集合并,第二遍检查所有 e=0(不等)约束是否出现 find(x)==find(y) 的矛盾(即相等与不等冲突),若存在冲突输出 NO,否则输出 YES;注意必须先处理全部相等关系再检查不等关系,否则传递性会导致误判。
AcWing 238 银河英雄传说 简单 ⭐⭐⭐⭐ 带权并查集(距离向量) d[x] 记录战舰 \(x\) 到所在列队首的战舰间隔数,siz[x] 记录以 \(x\) 为根的列的战舰总数;find(x) 递归找根的同时用 d[x] += d[p[x]] 累加父节点到根的距离并路径压缩;M a b 合并时将 \(a\) 列接在 \(b\) 列尾部,设 d[pa] = siz[pb]\(a\) 列根到 \(b\) 列根的距离为 \(b\) 列大小),更新 siz[pb] += siz[pa] 后连边 p[pa] = pbC a b 时若同根则输出 abs(d[a]-d[b])-1(两舰之间的战舰数),不同根输出 `-1$。
AcWing 239 奇偶游戏 中等 ⭐⭐⭐⭐ 带权并查集(异或权值) + 离散化 + 前缀和转化 将区间 \([l,r]\) 中 1 的个数的奇偶性转化为前缀和异或关系 sum[r] ^ sum[l-1] = tt=0 为偶,t=1 为奇),用 unordered_map\(l-1\)\(r\) 做离散化;find(x) 递归找根的同时用 d[x] ^= d[p[x]] 更新到根节点的异或权值并路径压缩,若 \(a\)\(b\) 同根则检查 d[a]^d[b] == t 是否成立,不同根则合并并设 d[pa] = d[a]^d[b]^t 保证新集合奇偶关系一致,遇到矛盾时输出前 \(i-1\) 个回答为最大自洽数。
AcWing 240 食物链 中等 ⭐⭐⭐⭐ 带权并查集(扩展域 / 距离向量) 带权并查集维护每个节点到根节点的距离 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 的情况为假话,累计假话数输出。
AcWing 784 强盗团伙
AcWing 836 合并集合 简单 并查集(路径压缩) 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(\alpha(N))\)
AcWing 837 连通块中点的数量 简单 ⭐⭐ 并查集(路径压缩 + 按大小合并) 在基础并查集上增加 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(\alpha(N))\)
AcWing 1249 亲戚
AcWing 1250 格子游戏
AcWing 1251 打击犯罪
AcWing 1252 搭配购买
AcWing 1253 家谱
AcWing 6058 亲戚
洛谷 [[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 三值逻辑 普及+
AtCoder AT_awc0080_e Paint Drop ⭐⭐⭐⭐ 并查集(按行维护 + 路径压缩) 对每行独立维护并查集 nxt[i][j] 表示第 \(i\) 行中位置 \(j\)下一个未涂色位置,初始化 nxt[i][j] = j;每次查询时遍历菱形区域内的每一行,计算该行的列区间 \([j_L, j_R]\),用 find(i, j_L) 找到第一个未涂色位置,累加分数后将该位置指向 find(i, j+1)(标记已涂色),再继续 find 查找下一个未涂色位置直到超出 \(j_R\)find 函数带路径压缩,保证每个格子最多被访问一次,总时间复杂度近似 \(O(H \times W \times \alpha(W) + Q \times D_{max} \times \alpha(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)\)
AcWing [[246 区间最大公约数]] 困难 ⭐⭐⭐⭐ 线段树 + 树状数组 + 差分数组 利用数学性质 \(\gcd(A[l..r]) = \gcd(A[l], \gcd(b[l+1..r]))\)(其中 \(b[i]=A[i]-A[i-1]\) 为差分数组),将区间加转化为差分数组的单点修改;用树状数组维护差分数组前缀和以 \(O(\log N)\) 查询 \(A[l]\),用线段树维护差分数组的区间 GCD 以 \(O(\log N)\) 查询 \(\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\) 时直接输出 abs(A[l])),单次操作 \(O(\log N)\)
AcWing 247 亚特兰蒂斯 困难 ⭐⭐⭐⭐⭐ 扫描线 + 线段树 + 离散化 将每个矩形拆分为入边\(x=x_1, k=+1\))和出边\(x=x_2, k=-1\))两条扫描线,对所有 \(y\) 坐标离散化后建立线段树维护 \(y\) 方向覆盖长度 len 和覆盖次数 cntpushup 时若 cnt>0len 为区间实际长度,否则继承子节点之和;按 \(x\) 排序扫描线后从左到右遍历,每次用 tr[1].len * (seg[i].x - seg[i-1].x) 累加条带面积,再用 update 将当前边的 \(y\) 范围加入或移除覆盖(右端点用 find(y2)-1 因为节点 \(i\) 代表区间 \([alls[i], alls[i+1]]\)),最终得到所有矩形面积并。
AcWing 1275 最大数 中等 ⭐⭐⭐ 线段树(单点修改 + 区间查询) 线段树维护动态序列的区间最大值,建树时覆盖 \([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(m \log m)\)
AcWing 1277 维护序列 困难 ⭐⭐⭐⭐ 线段树 + 双懒标记(乘法+加法) 用线段树维护区间和,每个节点同时存乘法懒标记 mul 和加法懒标记 add,下传时按"先乘后加"顺序 evalsum = sum*mul + len*addadd = add*mul + add_new),支持区间乘、区间加、区间求和,时间复杂度 \(O(m \log n)\),空间复杂度 \(O(n)\)
洛谷 AT_abc340_e Mancala 2 普及+
洛谷 [[P1198 最大数]] 普及+
洛谷 P3372 线段树1 普及+
洛谷 AT_abc360_f InterSections 提高+
洛谷 [[P3114 Stampede]] 提高+
洛谷 P1600 天天爱跑步 省选
AtCoder AT_awc0005_e Mountain Height Survey
AtCoder AT_awc0009_e Temperature Fluctuation Survey
AtCoder AT_awc0014_e Flowerbed Watering Management
AtCoder AT_awc0020_e Shelving Books on a Bookshelf ⭐⭐⭐ 线段树(区间最大值 + 在线二分查找) 用线段树维护每个书架位置的最大承重能力find 函数优先在左子树二分查找第一个容量 >= W[i] 的位置,若当前区间最大值 < W[i] 则剪枝返回 -1;找到位置后 update 将其设为 -INF 标记已占用,成功放置则计数器加一;按书顺序依次处理,总时间复杂度 \(O((N+M) \log M)\)
AtCoder AT_awc0062_e Radio Tower and Signal Strength ⭐⭐⭐⭐ 差分 + 前缀和 + 线段树(区间最值) 每个信号源 \((X_i, B_i)\) 的贡献是三角形函数(中心 \(X_i\) 处为 \(B_i\),向两侧线性递减到 0),用差分数组记录斜率变化:在 \([X_i-B_i+1, X_i]\) 区间斜率 +1,在 \([X_i+1, X_i+B_i]\) 区间斜率 -1,即 diff[x-b+1] += 1diff[x+1] -= 2diff[x+b+1] += 1;对差分数组做两次前缀和(斜率→信号强度)得到每个整数坐标 \(p\)\(f(p)\) 值;最后用线段树维护区间最大值,每次查询 \([L_j, R_j]\) 的区间最大值并输出,坐标用 OFFSET=200000 偏移处理负数,总时间复杂度 \(O(N + M + Q \log M)\)\(M=400000\) 为坐标范围)。
AtCoder AT_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)\)
AtCoder AT_awc0081_e Balanced Groups in Tournament Partition ⭐⭐⭐⭐⭐ 线段树 + 滑动窗口 + 完全二叉树性质 递归二分分组天然形成完全二叉树,线段树节点值维护区间内 1 的数量(白帽人数),val 从 1 开始每层翻倍表示该层组应含叶子数,若 tr[i] == val/2 则该组为平衡组,用 acc 统计当前平衡组数量;滑动窗口枚举操作区间 \([l, l+K-1]\),将区间内 0 移到左、1 移到右等价于把窗口内 cnt1 集中到右侧,通过 update 单点修改并自底向上更新 tracc(跨越平衡条件时增减计数),每次窗口移动后 res = max(res, acc),最终输出最大平衡组数;时间复杂度 \(O(2^N \times N)\)
AtCoder AT_awc0082_e Company Organization and Salaries ⭐⭐⭐⭐⭐ DFS 序 + 线段树套树状数组(树套树) + 离散化 先通过 DFS 求欧拉序 将子树转化为连续区间 \([In[v], Out[v]]\)离线收集所有初始工资和修改操作中的新工资,对每个线段树节点单独离散化值域后建立树状数组1 v x 修改时在 In[v] 位置删除旧工资、加入新工资,2 v 查询时在线段树区间 \([In[v], Out[v]]\) 中统计值域 \([A[v]+1, INF]\) 的个数并输出;外层线段树维护 DFS 序区间,内层树状数组维护值域频次,单次操作 \(O(\log^2 N)\)
AtCoder AT_awc0087_e Change of Assigned Interval ⭐⭐⭐⭐ 线段树(最大子段和) + 前缀和 A 类任务设为 \(+P_i\)T 类任务设为 \(-P_i\) 得到数组 \(v\),用前缀和 sa 统计不翻转时青木的基础成本;翻转区间 \([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)\)

【树状数组】

算法模板

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]);  // 存储差分值

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
AcWing 241 楼兰图腾 简单 ⭐⭐⭐ 树状数组(前缀和统计) 从左到右扫描,用树状数组维护已出现元素的集合,query(n)-query(y) 得到左边比 \(a[i]\) 大的元素个数记为 Greater[i]query(y-1) 得到左边比 \(a[i]\) 小的元素个数记为 Lower[i];清空树状数组后从右到左扫描,用 Greater[i] * (query(n)-query(y)) 累加 V 图腾数(中间点 \(j\) 左边有 Greater[i] 个比它大的,右边有 query(n)-query(y) 个比它大的),用 Lower[i] * query(y-1) 累加 图腾数(中间点 \(j\) 左边有 Lower[i] 个比它小的,右边有 query(y-1) 个比它小的),每次扫描后 add(y,1) 将当前元素加入树状数组,总时间复杂度 \(O(N \log N)\)
AcWing 242 一个简单的整数问题
AcWing 243 一个简单的整数问题2
AcWing 244 谜一样的牛
洛谷 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 配对统计 提高+
AtCoder AT_awc0006_e Store Sales Management
AtCoder AT_awc0008_e Organizing the Bookshelf
AtCoder AT_awc0015_e Count the Types of Flowers ⭐⭐⭐ 树状数组 + 离线查询(按右端点排序) 将查询按右端点升序排序,从左到右扫描数组,用 last[x] 记录物种 \(x\)最后出现位置,树状数组维护"当前扫描范围内每个物种最右出现位置"的标记(值为 1);当物种 \(x\) 在位置 \(i\) 出现时,若 last[x] != 0 则先在旧位置 add(last[x], -1) 取消标记,再在当前位置 add(i, 1) 添加新标记,更新 last[x] = i;回答查询时 ans[id] = query(r) - query(l-1) 统计区间 \([l,r]\) 内有效标记数(即不同物种数),最后按原始编号输出答案,总时间复杂度 \(O((N+Q) \log N)\)
AtCoder AT_awc0025_e Organizing the Bookshelf ⭐⭐⭐ 树状数组 + 二分查找(第 k 小 / 顺序统计) 树状数组维护每个位置是否存在书(\(1\) 表示存在,\(0\) 表示已移除),query(x) 返回前 \(x\) 个位置中书的数量;find(k) 通过二分查找定位第 \(k\) 本书的实际位置(找最小的 pos 使得 query(pos) >= k);每次操作若 \(T_j > tn\)(当前剩余书数)则直接输出 tn,否则找到实际位置 pos 后将 d[pos]--,若耐久度降为 \(0\)add(pos,-1) 标记移除并 tn--,最后输出 tn;每次操作 \(O(\log^2 N)\)
AtCoder AT_awc0027_e Selection of Contiguous Intervals ⭐⭐⭐⭐ 树状数组 + 离散化 将条件转化为前缀和形式:定义 \(sa[i] = \sum_{j=1}^{i}(A_j + M)\),则 \(f(l,r) \leq K\) 等价于 \(sa[l-1] \geq sa[r] - K\);收集所有 \(sa[i]\)\(sa[i]-K\) 离散化后,遍历右端点 \(r\),用树状数组查询已插入的 \(sa[l-1]\) 中大于等于 \(sa[r]-K\) 的数量(总数减前缀和),再将 \(sa[r]\) 插入树状数组,时间复杂度 \(O(N \log N)\)
AtCoder AT_awc0032_e Multiple Bonus ⭐⭐⭐⭐ 树状数组 + 分块优化(阈值分治) \(B = \sqrt{N}\) 为阈值,\(k\)\(k \leq B\)tag[k] 标记累计增量,查询时通过 tag[k] * (x/k) 快速计算前 \(x\) 个位置中 \(k\) 的倍数个数得到总贡献;\(k\)\(k > B\)&#x76F4;接遍历 \(i=k,2k,3k,\ldots \leq N\) 用树状数组 add(i,v) 单点修改,查询时 query(x) 累加;最终答案为 sa[x] + query(x) + Σ(tag[k]*(x/k)),修改操作小 \(k\)\(O(1)\)、大 \(k\)\(O(N/k) \leq O(\sqrt{N})\),查询为 \(O(\sqrt{N}+\log N)\)
AtCoder AT_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)\)

【单调栈】

算法模板

// 常见模型:找出每个数左边离它最近的比它大/小的数
// 输入数组元素
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;
}

算法应用

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

【哈希表】

算法模板

拉链法

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

算法应用

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

【平衡树】

算法模板

算法应用

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

【动态树】

算法模板

算法应用

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

【主席树】

算法模板

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
AcWing 256 最大异或和 中等 ⭐⭐⭐⭐⭐ 可持久化字典树(主席树) 前缀异或和转化后,问题变为在区间 \([l-1, r-1]\) 内找与 \(C=s[N] \oplus x\) 异或最大的 \(s[p-1]\),用可持久化字典树维护历史版本,每个节点存子树最大下标 max_id,查询时从高位到低位贪心走异或分支(需满足 max_id >= l-1),时间复杂度 \(O((N+M) \log V)\)\(V=2^{24}\)),空间复杂度 \(O((N+M) \log V)\)
洛谷 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\sim n\))和小根堆性质(权值 \(p[i]\) 父节点小于子节点);用单调递增栈(栈顶元素权值最大)维护当前右链,对于新元素 \(i\):从栈顶弹出所有权值 \(> p[i]\) 的元素,若存在出栈元素则最后出栈的元素成为 \(i\)左儿子t[i].ls = stk[pos+1]),若栈非空则 \(i\) 成为栈顶元素的右儿子t[stk[pos]].rs = i),然后将 \(i\) 入栈;每个节点最多入栈出栈一次,时间复杂度 \(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\) 维护当前区间,通过 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)\) 时间内增量更新答案;每次移动指针后将 sum 存入对应询问编号的位置,最后按原始顺序输出;分块大小 \(B = \sqrt{n}\),总时间复杂度 \(O((n+m)\sqrt{n})\)
AtCoder AT_awc0088_e Intervals That Can Be Arranged Alternately ⭐⭐⭐⭐ 莫队算法 + 前缀差分转化 定义前缀差分数组 \(\text{cumul}[i]\) 为前 \(i\) 个石头中白减黑的数量,区间 \([l,r]\) 能交替排列(好区间)当且仅当 $\
posted @ 2026-06-27 14:22  团爸讲算法  阅读(16)  评论(0)    收藏  举报