【学习笔记】势能线段树——线段树的小套路,让暴力显得更加优美
一些同类型的套路
线段树维护的区间和要具有可加性,并且对于区间的修改需要打lazy
标记或者标记永久化。
然而,一些操作,比如对 \(l\) 到 \(r\) 区间内的每个数开方、取模等不具备可整体修改性质的操作就无从下手了,并且(至少是我知道的)别的数据结构们也维护不了这些操作。
但是,这些仍然可以用线段树来维护,只是需要一点点的小变动。
P4145 上帝造题的七分钟 2 / 花神游历各国
题意概述:
维护一个序列,支持区间开方与求和操作。
解析:
区间每个数都开方的话,显然无法在sum
上直接操作,然而暴力的单点修改显然会超时,而 lazy
标记显然也无法胜任。
本题的精髓在于 \(\sqrt{1} = 1\)。
最大的数据是 \(1e12\) 的,向下取整后,最多进行 \(6\) 次sqrt
操作后就能到达 \(1\)。
所以,我们可以记录下当前区间的最大值,如果该区间的最大值 \(≤ 1\) 了,就可以不用管它了,否则,我们对每个区间都直接递归到底。
因为最多进行六次修改就到 \(1\) 了,所以均摊时间复杂度还是 \(O(\log{n})\) 的,总复杂度是 \(O(n\log{n})\) 的,可过。
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
标记维护。
思路大致是一样的,同样维护区间的最大值,比较区间最大值是否大于等于模数,如果大于等于,进行递归,直到叶子节点。
均摊时间复杂度仍然是 \(O(\log{n})\) 的,可过。
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
题意概述:
维护一个序列,支持把区间内的每一个数 \(i\),都变成 \(\tau(i)\) 和求和。(\(\tau(i)\) 表示 \(i\) 的正约数个数)。
解析:
看到这,聪明的你应该已经明白套路了吧。
先考虑怎么求出 \(\tau(i)\)。
有唯一分解定理:\(p = {p_{1}}^{k_{1}} \times {p_{2}}^{k_{2}} \times ... \times {p_{n}}^{k_{n}}\)。
那 \(\tau(p) = \prod_{i = 1}^{n}(k_{i} + 1)\)。
但每次这样求是 \(O(\sqrt{n})\) 的,会超时。
考虑线性筛,每个数有三种情况:
- \(i\) 是素数:
则 \(i\) 只能被 \(1\) 和它本身整除,所以:$$\tau(i) = 2$$ - \(i\) 与一个不能整除它的素数相乘(设素数为 \(p\)):
\(i\) 与 \(p\) 除 \(1\) 外没有相同的正约数,所以 \(i\) 的每个正约数和 \(p\) 的每个正约数分别相乘,得到的都是 \(i \times p\) 的正约数,且两两不同,所以:$$\tau(i) \times \tau(p)$$ 即: $$\tau(i \times p) = \tau(i) \times 2$$ - \(i\) 与一个能整除它的素数相乘(设素数为 \(p\)):
\(i\) 和 \(p\) 有除 \(1\) 外的正约数,则 \(\tau(i) \times \tau(p)\) 时,\(i / p\) 的正约数在乘以 \(p\) 后都会变成 \(i\) 的正约数。
在 \(i\) 的正约数与 \(p\) 和 \(1\)(他们是 \(p\) 仅有的正约数)相乘后,相当于每个 \(i / p\) 的正约数乘以 \(p\) 的积都出现了两遍,所以:$$\tau(i \times p) = \tau(i) \times 2 - \tau(i / p)$$
特别的 \(\tau(0) = 0, \tau(1) = 1\)。
之后,我们发现,最大的 \(\tau(i) \approx \frac{i}{2}\),所以仍然能保证复杂度是 \(O(\log n)\) 的。
按照原套路,记录区间的最大值是否 \(≥ 1\) 来判断需不需要递归到叶子节点即可。
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