牛客竞赛数据结构专题班势能线段树、李超线段树
zngg的题解
势能线段树模板题一
题意:
操作一:对区间
[
l
,
r
]
[l,r]
[l,r] 每个数
x
x
x 取
x
\sqrt{x}
x
操作二:求区间
[
l
,
r
]
[l,r]
[l,r] 的和
思路一:
我们没有办法直接对区间进行开方然后下传,所以pushdown就没必要写了。
对于每个数,开方6次之后达到势能上限,其结果必然为0或者1。那么对于整个区间,
可以记录开方次数(取区间内开方次数最少的那个值)是否达到6就可以了,
或者只要一个区间全为0或1的话,也可以直接返回。
code:
#include<bits/stdc++.h>
#define ll long long
#define lc (p << 1)
#define rc (p << 1 | 1)
#define endl '\n'
using namespace std;
const int maxn = 2e5 + 9;
int n, m;
ll tr[maxn << 2], cnt[maxn << 2];
inline void pushup(int p)
{
tr[p] = tr[lc] + tr[rc];
cnt[p] = min(cnt[lc], cnt[rc]);
}
void build(int p = 1, int cl = 1, int cr = n)
{
if(cl == cr){
cin >> tr[p];
return;
}
int mid = (cl + cr) >> 1;
build(lc, cl, mid);
build(rc, mid + 1, cr);
pushup(p);
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
if(cl >= l && cr <= r) return tr[p];
ll mid = (cl + cr) >> 1, ans = 0;
if(mid >= l) ans += query(l, r, lc, cl, mid);
if(mid < r) ans += query(l, r, rc, mid + 1, cr);
return ans;
}
void update(int l, int r, int p = 1, int cl = 1, int cr = n)
{
if(cnt[p] > 5) return;
if(cl == cr){
tr[p] = (int)sqrt(tr[p]);
++cnt[p];
return;
}
int mid = (cl + cr) >> 1;
if(mid >= l) update(l, r, lc, cl, mid);
if(mid < r) update(l, r, rc, mid + 1, cr);
pushup(p);
}
void work()
{
cin >> n >> m;
build();
while(m--)
{
int op, l, r;cin >> op >> l >> r;
if(op == 1) update(l, r);
else cout << query(l, r) << endl;
}
}
int main()
{
ios::sync_with_stdio(0);
work();
return 0;
}
思路二:
框架基本一样
维护区间最大值,如果区间最大值为
1
1
1,则没必要修改
code:
#include<bits/stdc++.h>
#define ll long long
#define lc (p << 1)
#define rc (p << 1 | 1)
#define endl '\n'
using namespace std;
const int maxn = 2e5 + 9;
const ll inf = 0x3f3f3f3f3f3f;
int n, m;
ll tr[maxn << 2], Max[maxn << 2];
inline void pushup(int p)
{
tr[p] = tr[lc] + tr[rc];
Max[p] = max(Max[lc], Max[rc]);
}
void build(int p = 1, int cl = 1, int cr = n)
{
Max[p] = - inf;// 因为 ai 始终是正的,所以不置为 -inf,初始全是 0 也是一样的
if(cl == cr){
cin >> tr[p];
return;
}
int mid = (cl + cr) >> 1;
build(lc, cl, mid);
build(rc, mid + 1, cr);
pushup(p);
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
if(cl >= l && cr <= r) return tr[p];
ll mid = (cl + cr) >> 1, ans = 0;
if(mid >= l) ans += query(l, r, lc, cl, mid);
if(mid < r) ans += query(l, r, rc, mid + 1, cr);
return ans;
}
void update(int l, int r, int p = 1, int cl = 1, int cr = n)
{
if(Max[p] == 1) return;
if(cl == cr){
tr[p] = (int)sqrt(tr[p]);
Max[p] = tr[p];
return;
}
int mid = (cl + cr) >> 1;
if(mid >= l) update(l, r, lc, cl, mid);
if(mid < r) update(l, r, rc, mid + 1, cr);
pushup(p);
}
void work()
{
cin >> n >> m;
build();
while(m--)
{
int op, l, r;cin >> op >> l >> r;
if(op == 1) update(l, r);
else cout << query(l, r) << endl;
}
}
int main()
{
ios::sync_with_stdio(0);
work();
return 0;
}
势能线段树模板题二
题意:
操作一:对区间
[
l
,
r
]
[l,r]
[l,r] 每个数
x
x
x 取
x
\sqrt{x}
x
操作二:对区间
[
l
,
r
]
[l,r]
[l,r] 每个数 加
c
c
c
操作三:求区间
[
l
,
r
]
[l,r]
[l,r] 的和
思路:
带修改的区间开方
我们发现对于一个序列,如果开方的前后,最大值的变化量等于最小值的变化量,那么其实这就是一个区间减的操作(因为显然这个变化量是有单调性的),因此我们只需要维护区间最大值和区间最大值即可,然后暴力修改,遇到满足条件的就区间减,然后直接return
code:
#include<bits/stdc++.h>
#define ll long long
#define lc (p << 1)
#define rc (p << 1 | 1)
#define endl '\n'
using namespace std;
const int maxn = 2e5 + 9;
const ll inf = 0x3f3f3f3f3f3f;
ll n, m;
ll a[maxn];
struct node
{
ll add, sum, Max, Min;
}tr[maxn << 2];
inline void addlazy(int p, int len, ll x)
{
tr[p].add += x;
tr[p].sum += 1ll * len * x;
tr[p].Max += x;
tr[p].Min += x;
}
inline void pushup(int p)
{
tr[p].sum = tr[lc].sum + tr[rc].sum;
tr[p].Max = max(tr[lc].Max, tr[rc].Max);
tr[p].Min = min(tr[lc].Min, tr[rc].Min);
}
inline void pushdown(int p, int len)
{
if(!tr[p].add) return;
addlazy(lc, len - len / 2, tr[p].add);
addlazy(rc, len / 2, tr[p].add);
tr[p].add = 0;
}
void build(int p = 1, int cl = 1, int cr = n)
{
tr[p].Max = -inf; tr[p].Min = inf;
if(cl == cr){
tr[p].Max = tr[p].Min = tr[p].sum = a[cl];
return;
}
int mid = (cl + cr) >> 1;
build(lc, cl, mid);
build(rc, mid + 1, cr);
pushup(p);
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
if(cl >= l && cr <= r) return tr[p].sum;
pushdown(p, cr - cl + 1);
ll mid = (cl + cr) >> 1, ans = 0;
if(mid >= l) ans += query(l, r, lc, cl, mid);
if(mid < r) ans += query(l, r, rc, mid + 1, cr);
return ans;
}
void update(int l, int r, ll d, int p = 1, int cl = 1, int cr = n)
{
if(cl >= l && cr <= r){
if(d == -1)
{
ll fx = tr[p].Max - (ll)sqrt(tr[p].Max);
ll fy = tr[p].Min - (ll)sqrt(tr[p].Min);
if(fx == fy) {
addlazy(p, cr - cl + 1, -fx);return;
}
}
else if(d != -1) {
addlazy(p, cr - cl + 1, d);return;
}
}
pushdown(p, cr - cl + 1);
int mid = (cl + cr) >> 1;
if(mid >= l) update(l, r, d, lc, cl, mid);
if(mid < r) update(l, r, d, rc, mid + 1, cr);
pushup(p);
}
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i];
build();
while(m--)
{
ll op, l, r;cin >> op >> l >> r;
if(op == 1) update(l, r, -1);
else if(op == 2){
ll k;cin >> k; update(l, r, k);
}
else cout << query(l, r) << endl;
}
}
int main()
{
ios::sync_with_stdio(0);
work();
return 0;
}
Gorgeous Sequence
题意:
三个操作
- 对区间 [ l , r ] [l,r] [l,r] ,每个数与 t t t 取 m i n min min,即 a i = m i n ( a i , t ) a_i=min(a_i,t) ai=min(ai,t)
- 查询区间 [ l , r ] [l,r] [l,r] 最大值
- 查询区间 [ l , r ] [l,r] [l,r] 所有元素之和
思路:
区间取
m
i
n
min
min,意味着只对那些大于
t
t
t 的数有更改。因此这个操作的对象不再是整个区间,而是“这个区间中大于
t
t
t 的数”。于是我们可以有这样的思路:每个结点维护该区间的最大值
M
a
x
Max
Max 、次大值
S
e
m
a
x
Semax
Semax、区间和
S
u
m
Sum
Sum 以及最大值的个数
C
n
t
Cnt
Cnt。接下来我们考虑区间对
t
t
t 取
m
i
n
min
min 的操作。
- 如果 M a x < = t Max<=t Max<=t,显然这个 t t t 是没有意义的,直接 r e t u r n return return
- 如果 S e m a x < t < M a x Semax<t<Max Semax<t<Max,那么这个 t t t 就能更新当前区间中的最大值。于是我们让区间和加上 C n t ∗ ( t − M a x ) Cnt*(t-Max) Cnt∗(t−Max),然后更新 M a x Max Max 为 t t t,并打一个标记。
- 如果 t < = S e m a x t<=Semax t<=Semax,这种情况我们不知道涉及多少数的过呢更新,就暴力递归向下操作,然后上传信息。
复杂度
O
(
m
l
o
g
n
)
O(m \ log \ n)
O(m log n)
code:
#include<bits/stdc++.h>
#define ls (p << 1)
#define rs (p << 1 | 1)
#define ll long long
using namespace std;
const int maxn = 1e6 + 9;
int n, m;
struct node
{
ll sum;
int Max, Semax, cnt, mark;
}tr[maxn << 2];
inline void pushlazy(int p, int x)
{
if(tr[p].Max <= x) return;
tr[p].sum += (1ll * x - tr[p].Max) * tr[p].cnt;
tr[p].Max = tr[p].mark = x;
}
inline void pushdown(int p)
{
if(tr[p].mark == -1) return;
pushlazy(ls, tr[p].mark);
pushlazy(rs, tr[p].mark);
tr[p].mark = -1;
}
inline pushup(int p)
{
tr[p].sum = tr[ls].sum + tr[rs].sum;
if(tr[ls].Max == tr[rs].Max)
{
tr[p].Max = tr[ls].Max;
tr[p].Semax = max(tr[ls].Semax, tr[rs].Semax);
tr[p].cnt = tr[ls].cnt + tr[rs].cnt;
}
else if(tr[ls].Max > tr[rs].Max)
{
tr[p].Max = tr[ls].Max;
tr[p].Semax = max(tr[ls].Semax, tr[rs].Max);
tr[p].cnt = tr[ls].cnt;
}
else
{
tr[p].Max = tr[rs].Max;
tr[p].Semax = max(tr[ls].Max, tr[rs].Semax);
tr[p].cnt = tr[rs].cnt;
}
}
void build(int p = 1, int cl = 1, int cr = n)
{
tr[p].mark = -1;
if(cl == cr){
cin >> tr[p].sum;
tr[p].Max = tr[p].sum;
tr[p].cnt = 1;
tr[p].Semax = -1;
return;
}
int mid = (cl + cr) >> 1;
build(ls, cl, mid);
build(rs, mid + 1, cr);
pushup(p);
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
if(tr[p].Max <= d) return;
if(cl >= l && cr <= r && tr[p].Semax < d){
pushlazy(p, d);
return;
}
int mid = (cl + cr) >> 1;
pushdown(p);
if(mid >= l) update(l, r, d, ls, cl, mid);
if(mid < r) update(l, r, d, rs, mid + 1, cr);
pushup(p);
}
int query_max(int l, int r, int p = 1, int cl = 1, int cr = n)
{
if(cl >= l && cr <= r) return tr[p].Max;
int mid = (cl + cr) >> 1, ans1 = -1, ans2 = -1;
pushdown(p);
if(mid >= l) ans1 = max(ans1, query_max(l, r, ls, cl, mid));
if(mid < r) ans2 = max(ans2, query_max(l, r, rs, mid + 1, cr));
return max(ans1, ans2);
}
ll query_sum(int l, int r, int p = 1, int cl = 1, int cr = n)
{
if(cl >= l && cr <= r) return tr[p].sum;
pushdown(p);
ll mid = (cl + cr) >> 1, ans = 0;
if(mid >= l) ans += query_sum(l, r, ls, cl, mid);
if(mid < r) ans += query_sum(l, r, rs, mid + 1, cr);
return ans;
}
void work()
{
cin >> n >> m;
build();
while(m--)
{
int op, l, r, d;
cin >> op >> l >> r;
if(!op){
cin >> d; update(l, r, d);
}
else if(op == 1) cout << query_max(l, r) << endl;
else cout << query_sum(l, r) << endl;
}
}
int main()
{
ios::sync_with_stdio(0);
int TT;cin>>TT;while(TT--)
work();
return 0;
}