【学习笔记】势能线段树——线段树的小套路,让暴力显得更加优美
一些同类型的套路
线段树维护的区间和要具有可加性,并且对于区间的修改需要打lazy
标记或者标记永久化。
然而,一些操作,比如对 到 区间内的每个数开方、取模等不具备可整体修改性质的操作就无从下手了,并且(至少是我知道的)别的数据结构们也维护不了这些操作。
但是,这些仍然可以用线段树来维护,只是需要一点点的小变动。
P4145 上帝造题的七分钟 2 / 花神游历各国
题意概述:
维护一个序列,支持区间开方与求和操作。
解析:
区间每个数都开方的话,显然无法在sum
上直接操作,然而暴力的单点修改显然会超时,而 lazy
标记显然也无法胜任。
本题的精髓在于 。
最大的数据是 的,向下取整后,最多进行 次sqrt
操作后就能到达 。
所以,我们可以记录下当前区间的最大值,如果该区间的最大值 了,就可以不用管它了,否则,我们对每个区间都直接递归到底。
因为最多进行六次修改就到 了,所以均摊时间复杂度还是 的,总复杂度是 的,可过。
Code
#include<cmath> #include<cstdio> #include<algorithm> #define LL long long using namespace std; const int MAXN = 1e5 + 10; int n, m; LL num[MAXN]; struct Segment_Tree{ struct Tree{ int l, r; LL max; LL sum; }tr[MAXN << 2]; inline int lson(int rt){ return rt << 1; } inline int rson(int rt){ return rt << 1 | 1; } inline void Pushup(int rt){ tr[rt].sum = tr[lson(rt)].sum + tr[rson(rt)].sum; tr[rt].max = max(tr[lson(rt)].max, tr[rson(rt)].max); } void Build(int rt, int l, int r){ tr[rt].l = l; tr[rt].r = r; if(l == r){ tr[rt].sum = num[l]; tr[rt].max = num[l]; return; } int mid = (l + r) >> 1; Build(lson(rt), l, mid); Build(rson(rt), mid + 1, r); Pushup(rt); } void Update(int rt, int l, int r){ if(tr[rt].l == tr[rt].r){ tr[rt].sum = sqrt(tr[rt].sum); tr[rt].max = sqrt(tr[rt].max); return; } int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid && tr[lson(rt)].max > 1) Update(lson(rt), l, r); if(r > mid && tr[rson(rt)].max >1) Update(rson(rt), l, r); Pushup(rt); } LL Query_Sum(int rt, int l, int r){ if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].sum; int mid = (tr[rt].l + tr[rt].r) >> 1; if(r <= mid) return Query_Sum(lson(rt), l, r); else if(l > mid) return Query_Sum(rson(rt), l, r); else return Query_Sum(lson(rt), l, r) + Query_Sum(rson(rt), l, r); } }S; inline LL read(){ LL x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main(){ n = read(); for(register int i = 1; i <= n; i++) num[i] = read(); S.Build(1, 1, n); m = read(); for(register int i = 1; i <= m; i++){ int opt, l, r; opt = read(), l = read(), r = read(); if(l > r) swap(l, r); if(opt == 0) S.Update(1, l, r); else printf("%lld\n", S.Query_Sum(1, l, r)); } return 0; }
CF438D The Child and Sequence
题意概述:
维护一个序列,支持区间的取模、求和和单点修改。
解析:
同样的无法用lazy
标记维护。
思路大致是一样的,同样维护区间的最大值,比较区间最大值是否大于等于模数,如果大于等于,进行递归,直到叶子节点。
均摊时间复杂度仍然是 的,可过。
Code
#include<cstdio> #include<algorithm> using namespace std; const int MAXN = 1e5 + 10; int n, m; int num[MAXN]; struct Segment_Tree{ struct Tree{ int l, r; int max; long long sum; }tr[MAXN << 2]; inline int lson(int rt){ return rt << 1; } inline int rson(int rt){ return rt << 1 | 1; } inline void Pushup(int rt){ tr[rt].sum = tr[lson(rt)].sum + tr[rson(rt)].sum; tr[rt].max = max(tr[lson(rt)].max, tr[rson(rt)].max); } void Build(int rt, int l, int r){ tr[rt].l = l; tr[rt].r = r; if(l == r){ tr[rt].sum = tr[rt].max = num[l]; return; } int mid = (l + r) >> 1; Build(lson(rt), l, mid); Build(rson(rt), mid + 1, r); Pushup(rt); } void Update_Mod(int rt, int l, int r, int data){ if(tr[rt].l == tr[rt].r){ tr[rt].sum %= data; tr[rt].max %= data; return; } int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid && tr[lson(rt)].max >= data) Update_Mod(lson(rt), l, r, data); if(r > mid && tr[rson(rt)].max >= data) Update_Mod(rson(rt), l, r, data); Pushup(rt); } void Update_Point(int rt, int pos, int data){ if(tr[rt].l == tr[rt].r){ tr[rt].sum = data; tr[rt].max = data; return; } int mid = (tr[rt].l + tr[rt].r) >> 1; if(pos <= mid) Update_Point(lson(rt), pos, data); else Update_Point(rson(rt), pos, data); Pushup(rt); } long long Query_Sum(int rt, int l, int r){ if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].sum; long long ans = 0; int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid) ans += Query_Sum(lson(rt), l, r); if(r > mid) ans += Query_Sum(rson(rt), l, r); return ans; } }S; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main(){ n = read(), m = read(); for(register int i = 1; i <= n; i++) num[i] = read(); S.Build(1, 1, n); for(register int i = 1; i <= m; i++){ int opt; opt = read(); if(opt == 1){ int l, r; l = read(), r = read(); printf("%lld\n", S.Query_Sum(1, l, r)); } else if(opt == 2){ int l, r, x; l = read(), r = read(), x = read(); S.Update_Mod(1, l, r, x); } else{ int k, x; k = read(), x = read(); S.Update_Point(1, k, x); } } return 0; }
CF920F SUM and REPLACE
题意概述:
维护一个序列,支持把区间内的每一个数 ,都变成 和求和。( 表示 的正约数个数)。
解析:
看到这,聪明的你应该已经明白套路了吧。
先考虑怎么求出 。
有唯一分解定理:。
那 。
但每次这样求是 的,会超时。
考虑线性筛,每个数有三种情况:
- 是素数:
则 只能被 和它本身整除,所以: - 与一个不能整除它的素数相乘(设素数为 ):
与 除 外没有相同的正约数,所以 的每个正约数和 的每个正约数分别相乘,得到的都是 的正约数,且两两不同,所以: 即: - 与一个能整除它的素数相乘(设素数为 ):
和 有除 外的正约数,则 时, 的正约数在乘以 后都会变成 的正约数。
在 的正约数与 和 (他们是 仅有的正约数)相乘后,相当于每个 的正约数乘以 的积都出现了两遍,所以:
特别的 。
之后,我们发现,最大的 ,所以仍然能保证复杂度是 的。
按照原套路,记录区间的最大值是否 来判断需不需要递归到叶子节点即可。
Code
#include<cstdio> #include<algorithm> using namespace std; const int MAXN = 3e5 + 10, MAXM = 3e6 + 10; int n, m, sum, max_a; int num[MAXN]; int prime[MAXM], v[MAXM], d[MAXM]; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } void Euler(int n){ sum = 0; d[1] = 1; for(register int i = 2; i <= n; i++){ if(!v[i]){ d[i] = 2; v[i] = i; prime[++sum] = i; } for(register int j = 1; j <= sum; j++){ if(prime[j] > v[i] || prime[j] * i > n) break; v[i * prime[j]] = prime[j]; if(i % prime[j]) d[i * prime[j]] = d[i] * 2; //等同于 d[i] * d[prime[j]] else d[i * prime[j]] = d[i] * 2 - d[i / prime[j]]; } } } struct Segment_Tree{ struct Tree{ int l, r; int max; long long sum; }tr[MAXN << 2]; inline int lson(int rt){ return rt << 1; } inline int rson(int rt){ return rt << 1 | 1; } inline void Pushup(int rt){ tr[rt].sum = tr[lson(rt)].sum + tr[rson(rt)].sum; tr[rt].max = max(tr[lson(rt)].max, tr[rson(rt)].max); } void Build(int rt, int l, int r){ tr[rt].l = l; tr[rt].r = r; if(l == r){ tr[rt].sum = tr[rt].max = num[l]; return; } int mid = (l + r) >> 1; Build(lson(rt), l, mid); Build(rson(rt), mid + 1, r); Pushup(rt); } void Update(int rt, int l, int r){ if(tr[rt].l == tr[rt].r){ tr[rt].sum = d[tr[rt].sum]; tr[rt].max = d[tr[rt].max]; return; } int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid && tr[lson(rt)].max > 2) Update(lson(rt), l, r); if(r > mid && tr[rson(rt)].max > 2) Update(rson(rt), l, r); Pushup(rt); } long long Query_Sum(int rt, int l, int r){ if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].sum; long long ans = 0; int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid) ans += Query_Sum(lson(rt), l, r); if(r > mid) ans += Query_Sum(rson(rt), l, r); return ans; } }S; int main(){ n = read(), m = read(); for(register int i = 1; i <= n; i++){ num[i] = read(); max_a = max(max_a, num[i]); } S.Build(1, 1, n); Euler(max_a); for(register int i = 1; i <= m; i++){ int opt; opt = read(); if(opt == 1){ int l, r; l = read(), r = read(); S.Update(1, l, r); } else{ int l, r; l = read(), r = read(); printf("%lld\n", S.Query_Sum(1, l, r)); } } return 0; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16661147.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理