1、acwing 245 线段树 + 思维
题面:给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
-
1 x y
,查询区间 x, y 中的最大连续子段和,即 m a x x ≤ l ≤ r ≤ y ∑ i = l r A [ i ] max_{x≤l≤r≤y} {\sum_{i=l}^{r}A[i]} maxx≤l≤r≤y∑i=lrA[i] -
2 x y
,把 A[x]的值改成 y。
解法
-
两个操作分别为:区间查询、单点修改 ---- 线段树
-
最大连续字段和:2 ,-1,3 ,sum=3,思考方法:
-
struct node{
int L,R; 区间左右端点
int tmax; 最大连续子段和
int lmax; 最大前缀和
int rmax; 最大后缀和
int sum; 区间和
}
-
最大连续子段和的求法:由于已经通过线段树分了区间
-
最大子段和 = max(左儿子,右儿子,中间部分)
- 如 8 -1 3 7 -2 9 中间部分为最大连续子段:3+7=10
- 因此 t m a x = m a x ( m a x ( 左 儿 子 t m a x , 右 t m a x ) , 左 儿 子 后 缀 + 右 儿 子 前 缀 ) tmax = max(max(左儿子tmax, 右tmax) , 左儿子后缀+右儿子前缀) tmax=max(max(左儿子tmax,右tmax),左儿子后缀+右儿子前缀)
- 前后缀的求法: 以前缀为例,分类讨论
- 左儿子的前缀
- 左儿子的sum + 右儿子的前缀
线段树代码主要注意点:
1、pushup操作:目的是将更新后的子节点值传回父节点,更新父节点
2、build操作的 递归终止条件 + 初始化
3、mid取的都是树根的中点: ( t r e e [ u ] . l + t r e e [ u ] . r ) / 2 (tree[u].l + tree[u].r) / 2 (tree[u].l+tree[u].r)/2
4、modify修改操作中只用针对递归返回的加 pushup
5、query查询操作:分类讨论,注意 l <= mid 与 r > mid
AC代码:
#include <bits/stdc++.h>
#define INF 0x3f3f3f
using namespace std;
const int N = 510000;
int n,m;
int a[N];
struct node{
int l,r;
int lmax, rmax, tmax , sum;
}tree[N*4];
void pushup(node &u, node &s1, node &s2){
u.tmax = max(max(s1.tmax , s2.tmax) , s1.rmax + s2.lmax);
u.lmax = max(s1.lmax , s1.sum + s2.lmax);
u.rmax = max(s2.rmax , s2.sum + s1.rmax);
u.sum = s1.sum + s2.sum;
}
void pushup(int u){
pushup(tree[u] , tree[u*2] , tree[u*2+1]);
}
void build(int u, int l, int r){
tree[u].l = l , tree[u].r = r;
if(l == r){ //递归条件 + 初始化
tree[u].sum = a[l];
tree[u].lmax = a[l];
tree[u].rmax = a[l];
tree[u].tmax = a[l];
return;
}
int mid = (l + r) / 2;
build(u*2, l ,mid);
build(u*2+1, mid+1, r);
pushup(u);
}
node query(int u, int l, int r){
if(tree[u].l >= l && tree[u].r <= r){
return tree[u];
}
int mid = (tree[u].l + tree[u].r) / 2; //是tree根节点的中间
node s1, s2, s;
if(r <= mid) return s1 = query(u*2, l, r);
if(l > mid) return s2 = query(u*2+1, l, r);
s1 = query(u*2, l, r);
s2 = query(u*2+1, l, r);
pushup(s, s1, s2);
return s;
}
void modify(int u, int x, int v){
if(tree[u].l == x && tree[u].r == x){
tree[u].sum = v;
tree[u].lmax = v;
tree[u].rmax = v;
tree[u].tmax = v;
}
else{
int mid = ( tree[u].l + tree[u].r ) / 2;
if(x <= mid) modify(u*2, x, v);
else modify(u*2+1, x, v);
pushup(u);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1, 1, n);
while(m--){
int k,x,y;
cin>>k>>x>>y;
if(k == 1){
if(x > y) swap(x, y);
node tmp = query(1, x, y);
cout<< tmp.tmax <<endl;
}
else{
modify(1, x, y);
}
}
return 0;
}
2、acw 246 线段树 + 差分 + gcd
题面:给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d
,表示把 $A[l],A[l+1],…,A[r] $都加上 d。Q l r
,表示询问 $A[l],A[l+1],…,A[r] $的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。
区间修改 + 区间查询 —> 差分的单点修改 + 区间查询
推导:
g c d ( a [ l ] , a [ l + 1 ] , . . , a [ R ] ) = g c d ( a [ l ] , a [ l + 1 ] − a [ l ] , a [ l + 2 ] − a [ l + 1 ] , . . . , a [ R ] − a [ R − 1 ] ) gcd(a[l], a[l+1], .. , a[R]) = gcd(a[l], a[l+1]-a[l] , a[l+2]-a[l+1] ,... ,a[R] - a[R-1]) gcd(a[l],a[l+1],..,a[R])=gcd(a[l],a[l+1]−a[l],a[l+2]−a[l+1],...,a[R]−a[R−1])
令$ c[i] = a[i] - a[i-1]$
则原式 $= gcd(a[l] , (c[l+1],…,c[R])) $
用差分数组 c 来作为树状数组的原数组 ,求 gcd就是求 L+1 ~ R的 gcd
a数组的区间修改:a[l] ~ a[R] 加 $d:c[l]+d, d[R+1]-d $
则求 $a[i] = sum(c[1] ~ c[i]) $
AC代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 510000;
ll n,m;
ll a[N] , c[N] ;
char ch;
struct node{
ll l,r;
ll sum, d; //d:区间的 gcd
}tree[N*4];
ll gcd(ll x,ll y){
if(y==0) return x;
if(x<0) x *= -1;
if(y<0) y *= -1;
if(x < y) return gcd(y,x);
return gcd(y, x%y);
}
void pushup(ll u){
tree[u].sum = tree[u*2].sum + tree[u*2+1].sum;
tree[u].d = gcd(tree[u*2].d , tree[u*2+1].d);
}
void build(ll u, ll l, ll r){
tree[u].l = l, tree[u].r = r;
if(l == r){
tree[u].sum = c[l];
tree[u].d = c[l];
return;
}
ll mid = (l+r)/2;
build(u*2, l, mid);
build(u*2+1, mid+1, r);
pushup(u);
}
void modify(ll u, ll x, ll val){
if(tree[u].l == x && tree[u].r == x){
tree[u].sum += val;
tree[u].d += val;
}
else{
ll mid = (tree[u].l + tree[u].r) / 2;
if(x<=mid) modify(u*2, x, val);
else modify(u*2+1, x, val);
pushup(u);
}
}
node query(ll u, ll l, ll r){
if(tree[u].l >= l && tree[u].r <= r){
return tree[u];
}
ll mid = (tree[u].l + tree[u].r) / 2;
node tmp = {l,r,0,0}, s1 = {l,r,0,0}, s2 = {l,r,0,0};
if(l <= mid) s1 = query(u*2, l , r);
if(r > mid) s2 = query(u*2+1, l , r);
tmp.sum = s1.sum + s2.sum;
tmp.d = gcd(s1.d , s2.d);
return tmp;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for(int i=1; i<=n; i++){
cin>>a[i];
c[i] = a[i] - a[i-1];
}
build(1,1,n+1);
while(m--){
cin>>ch;
ll l,r,d;
if(ch == 'C'){
cin>>l>>r>>d;
c[l] += d, c[r+1] -= d;
modify(1, l, d);
modify(1, r+1, -d);
}
else{
cin>>l>>r;
node tmp, tmp2;
ll k = 0;
tmp = query(1, l+1, r);
tmp2 = query(1, 1, l);
k = tmp.d;
a[l] = tmp2.sum;
cout<< gcd(a[l] , k) <<endl;
}
}
return 0;
}
3、acw 243 线段树 (经典pushdown操作)
pushdown操作: (区间修改 + 查询 都要)
1、sum 区间和:考虑当前结点和所有子节点的所有标记的当前区间和(并不考虑祖先结点标记)
2、add:懒标记上一次区间修改只传到了当前层,未往下传播
left.add += root.add;
left.sum += (left.r - right.l + 1) * root.add; //长度 * add = 总和
right.add += root.add;
right.sum += (right.r - right.l + 1) * root.add;
void push_down(int u){
auto &root = tree[u];
auto &left = tree[u*2];
auto &right = tree[u*2+1];
if(root.lazy){
left.lazy += root.lazy;
left.sum += (left.r - left.l + 1) * root.lazy;
right.lazy += root.lazy;
right.sum += (right.r - right.l + 1) * root.lazy;
root.lazy = 0;
}
}
给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d
,表示把 A [ l ] , A [ l + 1 ] , … , A [ r ] A[l],A[l+1],…,A[r] A[l],A[l+1],…,A[r]都加上 d。Q l r
,表示询问数列中第$ l∼r$个数的和。
对于每个询问,输出一个整数表示答案
区间修改 + 区间查询 pushdown登场
AC代码
// 区间修改 + 区间查询
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 210000;
ll n,m,a[N];
char ch;
struct node{
int l,r;
ll sum, lazy;
}tree[N * 4];
void pushup(int u){
tree[u].sum = tree[u*2].sum + tree[u*2+1].sum;
}
void push_down(int u){
auto &root = tree[u];
auto &left = tree[u*2];
auto &right = tree[u*2+1];
if(root.lazy){
left.lazy += root.lazy;
left.sum += (left.r - left.l + 1) * root.lazy;
right.lazy += root.lazy;
right.sum += (right.r - right.l + 1) * root.lazy;
root.lazy = 0;
}
}
void build(ll u, ll l, ll r){
tree[u].l = l, tree[u].r = r;
if(l == r){
tree[u].sum = a[l];
tree[u].lazy = 0;
return;
}
int mid = (l + r) / 2;
build(u*2, l , mid);
build(u*2+1, mid + 1 , r);
pushup(u);
}
ll query(ll u , ll l , ll r){
if(tree[u].l >= l && tree[u].r <= r){
return tree[u].sum;
}
push_down(u); //由于要查询子区间,因此必须由父节点对子节点产生影响
int mid = (tree[u].l + tree[u].r) / 2;
ll s=0,s1=0,s2=0;
if(l <=mid) s1 = query(u*2, l, r);
if(r > mid) s2 = query(u*2+1, l, r);
s = s1 + s2;
return s;
}
void modify(int u, int l, int r, int val){
if(tree[u].l >= l && tree[u].r <= r && r-l > 0){ //是一个区间
tree[u].sum += (tree[u].r - tree[u].l + 1) * val;
tree[u].lazy += val;
return;
}
push_down(u); //想要往下分裂,必须 pushdown
int mid = (tree[u].l + tree[u].r) / 2;
if(l <= mid) modify(u*2, l , r, val);
if(r > mid) modify(u*2+1, l, r, val);
pushup(u); // 修改了子区间的sum,要同时更新父节点
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
while(m--){
cin>>ch;
ll l,r,d;
if(ch == 'C'){
cin>>l>>r>>d;
modify(1, l , r, d);
}
else{
cin>>l>>r;
cout<< query(1, l, r)<<endl;
}
}
return 0;
}
3、acw 1277维护序列 线段树 + add标记 + mul标记
题面:
有如下三种操作形式:
- 把数列中的一段数全部乘一个值;
- 把数列中的一段数全部加一个值;
- 询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。
输入格式
第一行两个整数 N 和 P;
第二行含有 N 个非负整数,从左到右依次为 a 1 , a 2 , … , a N a_1,a_2,…,a_N a1,a2,…,aN;
第三行有一个整数 M,表示操作总数;
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
- 操作 1:
1 t g c
,表示把所有满足 t ≤ i ≤ g 的 a i t≤i≤g 的 a_i t≤i≤g的ai 改为 a i × c a_i×c ai×c; - 操作 2:
2 t g c
,表示把所有满足 t ≤ i ≤ g 的 a i t≤i≤g 的 a_i t≤i≤g的ai改为 a i + c a_i+c ai+c; - 操作 3:
3 t g
,询问所有满足$ t≤i≤g 的 a_i$ 的和模 P 的值。
同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。
同时涉及乘法与加法的懒标记,需要分类讨论出正确的执行顺序!
思路:
- a ∗ x + b a*x + b a∗x+b
- ( a + b ) ∗ x = > a ∗ x + b ∗ x (a+b)*x => a*x+b*x (a+b)∗x=>a∗x+b∗x
在往下传递懒标记的过程中,情况一直接先乘后加即可,而情况二先乘后加,加的是 b*x;
因此,易得操作策略:
- 乘法操作: m u l = m u l ∗ x mul = mul * x mul=mul∗x , a d d = a d d ∗ x add = add * x add=add∗x
- 加法操作: a d d = a d d + b add = add + b add=add+b
在pushdown的过程中:
- 先乘 mul 再加 add
- mul标记的更新:sum、mul、add都乘
- add标记的更新:add、sum直接加
AC代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 110000;
ll n,mod;
ll a[N], m;
struct node{
ll l,r;
ll sum, add, mul;
}tree[N*4];
void pushup(ll u){
tree[u].sum = (tree[u*2].sum + tree[u*2+1].sum) % mod;
}
void pushdown(ll u){
ll mul = tree[u].mul;
ll add = tree[u].add;
if(tree[u].mul!=1){
tree[u*2].sum = tree[u*2].sum * mul % mod;
tree[u*2+1].sum = tree[u*2+1].sum * mul % mod;
tree[u*2].mul = tree[u*2].mul * mul % mod;
tree[u*2+1].mul = tree[u*2+1].mul * mul % mod;
tree[u*2].add = tree[u*2].add * mul % mod; //!
tree[u*2+1].add = tree[u*2+1].add * mul % mod;
tree[u].mul = 1;
}
if(tree[u].add){
tree[u*2].sum = (tree[u*2].sum + (tree[u*2].r - tree[u*2].l + 1) * add % mod) % mod;
tree[u*2+1].sum = (tree[u*2+1].sum + (tree[u*2+1].r - tree[u*2+1].l + 1) * add % mod) % mod;
tree[u*2].add = (tree[u*2].add + add) % mod;
tree[u*2+1].add = (tree[u*2+1].add + add) % mod;
tree[u].add = 0;
}
}
void build(ll u , ll l , ll r){
tree[u].l = l, tree[u].r = r;
if(l == r){
tree[u].sum = a[l];
tree[u].mul = 1;
tree[u].add = 0;
return;
}
ll mid = (l + r) / 2;
build(u*2, l, mid);
build(u*2+1, mid+1, r);
pushup(u);
tree[u].mul = 1;
tree[u].add = 0;
}
void multiply(ll u, ll l, ll r, ll val){
if(tree[u].l >= l && tree[u].r <= r){
tree[u].sum = tree[u].sum * val % mod;
tree[u].mul = tree[u].mul * val % mod;
tree[u].add = tree[u].add * val % mod ;
return;
}
pushdown(u); //想要向下操作,必须传递标记
ll mid = (tree[u].l + tree[u].r) / 2;
if(l <= mid) multiply(u*2, l, r, val);
if(r > mid) multiply(u*2+1, l, r, val);
pushup(u);
}
void add_val(ll u, ll l, ll r, ll val){
if(tree[u].l >= l && tree[u].r <= r){
tree[u].sum = ( tree[u].sum + (tree[u].r - tree[u].l + 1 ) * val % mod ) % mod;
tree[u].add = ( tree[u].add + val ) % mod;
return;
}
pushdown(u);
ll mid = (tree[u].l + tree[u].r) / 2;
if(l <= mid) add_val(u*2, l , r, val);
if(r > mid) add_val(u*2+1, l, r, val);
pushup(u);
}
ll query(ll u, ll l, ll r){
if(tree[u].l >= l && tree[u].r <= r){
return tree[u].sum % mod;
}
pushdown(u);
ll mid = (tree[u].l + tree[u].r) / 2;
ll s=0,s1=0,s2=0;
if(l <= mid) s1 = query(u*2, l, r);
if(r > mid) s2 = query(u*2+1, l, r);
return (s1 + s2) % mod;
}
int main(){
cin>>n>>mod;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>m;
build(1,1,n);
while(m--){
int op,t,g,c;
cin>>op>>t>>g;
if(op==1){
cin>>c;
multiply(1, t, g, c);
}
else if(op == 2){
cin>>c;
add_val(1, t, g, c);
}
else{
cout<< query(1, t, g) % mod <<endl;
}
}
return 0;
}