owned this note
owned this note
Published
Linked with GitHub
---
tags: 題解
---
# 最大連續和-線段樹/分治
## 題序
給定一條長$N$的線段,並有$Q$次詢問
每次詢問有一個區間$[QL,QR]$,滿足$QL≤L≤R≤QR$中,求最大連續和
且若最大連續和小於$0$則輸出$0$
輸入說明:
第一行有兩個正整數$N$和$Q$,分別代表縣段長度及詢問次數
第二行有$N$個正整數$A_i$,代表線段$1$~$N$
接下來有$Q$行
每行有兩個正整數$QL$和$QR$,分別代表詢問的區間
$N≤100000$
$Q≤100000$
$-1000000000≤A_i≤1000000000$
$1≤QL≤QR≤N$
輸出說明:
輸出指定區間內的最大連續和
每個答案輸出完換行
範例輸入:
4 3
1 -2 3 4
1 1
1 4
2 2
範例輸出:
1
7
0
## 題解
//線段樹的細節觀念在這不祥細解釋,只講解和基本區間和線段數不同的分治觀念和pull
//不熟線段樹的可以去看我線段樹的筆記[線段樹](/v9MArO34RFq-eWTRT5avWA)
我們在線段樹的每個節點中都存入-區間從左端開始的最大連續和、區間從右端開始的最大連續和、區間和、區間最大連續和
當我們從樹的底部開始向上更新時我們要分別更新這四個值
#define $lm$ 從左端開始的最大連續和
#define $rm$ 從右端開始的最大連續和
#define $ans$ 區間最大連續和
#define $sum$ 區間和
### 從左端開始的最大連續和
父節點的$lm$只有兩種可能
1.左子節點的$lm$
2.左子節點的$sum$ $+$ 右子節點的$lm$
兩種取$max$就是父節點的$lm$
### 從右端開始的最大連續和
父節點的$rm$一樣只有兩種可能
1.右子節點的$rm$
2.右子節點的$sum$ + 左子節點的$rm$
兩種取$max$就是父節點的$rm$
### 區間最大連續和
父節點的區間最大連續和則有三種可能
1.左子節點的$ans$
2.右子節點的$ans$
3.左子節點的$rm$ + 右子節點的$lm$
三種取$max$就是父節點的$ans$
### 區間和
區間和就和一般的線段樹做法一樣
父節點的區間和等於左子節點$+$右子節點的和
## 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 1000005
int n,t,a[maxn];
struct node{
int lm;//從左端開始的最大連續和
int rm;//從右端開始的最大連續和
int ans;//區間最大連續和
int sum;//區間和
}tree[maxn*4];
node pull(node lc,node rc){
node f;
f.lm = max(lc.lm , lc.sum + rc.lm);
f.rm = max(rc.rm , rc.sum + lc.rm);
f.sum= lc.sum + rc.sum;
f.ans= max(max(lc.ans , rc.ans) , lc.rm + rc.lm);
return f;
}
void build(int l,int r,int rt){
if(l==r){
tree[rt].lm = tree[rt].rm = tree[rt].sum = tree[rt].ans = a[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1) , build(mid+1,r,rt<<1|1);
tree[rt] = pull(tree[rt<<1] ,tree[rt<<1|1]);
}
node query(int L,int R,int l,int r,int rt){
if(L <= l && R >= r )
return tree[rt];
int mid = (l+r) >> 1;
if(R <= mid)
return query(L,R,l,mid,rt<<1);
else if(L > mid)
return query(L,R,mid+1,r,rt<<1|1);
else{
node back;
back = pull (query(L,mid,l,mid,rt<<1),query(mid+1,R,mid+1,r,rt<<1|1));
return back;
}
}
signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> t;
for(int i=1;i<=n;i++)
cin >> a[i];
build(1,n,1);
while(t--){
int x,y;
cin >> x >> y;
if(x>y) swap(x,y);
int ans = query(x,y,1,n,1).ans;
if(ans < 0) ans = 0;
cout << ans << '\n';
}
return 0;
}
```
<!--
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,t,a[1000005];
struct node{
int lm,rm,sum,ans;
}tree[4000005];
void pull(int rt){
tree[rt].lm = max(tree[rt<<1].lm , tree[rt<<1].sum + tree[rt<<1|1].lm);
tree[rt].rm = max(tree[rt<<1|1].rm , tree[rt<<1|1].sum + tree[rt<<1].rm);
tree[rt].sum= tree[rt<<1].sum + tree[rt<<1|1].sum;
tree[rt].ans= max(max(tree[rt<<1].ans , tree[rt<<1|1].ans) , tree[rt<<1].rm + tree[rt<<1|1].lm);
}
void build(int l,int r,int rt){
if(l==r){
tree[rt].lm = tree[rt].rm = tree[rt].sum = tree[rt].ans = a[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1) , build(mid+1,r,rt<<1|1);
pull(rt);
}
node query(int L,int R,int l,int r,int rt){
if(L <= l && R >= r )
return tree[rt];
int mid = (l+r) >> 1;
if(R <= mid)
return query(L,R,l,mid,rt<<1);
else if(L > mid)
return query(L,R,mid+1,r,rt<<1|1);
else{
node back;
node lct = query(L,mid,l,mid,rt<<1);//左子節點
node rct = query(mid+1,R,mid+1,r,rt<<1|1);//右子節點
back.lm = max(lct.lm,lct.sum+rct.lm);
back.rm = max(rct.rm,rct.sum+lct.rm);
back.ans = max(max(lct.ans,rct.ans),lct.rm+rct.lm);
return back;
}
}
signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> t;
for(int i=1;i<=n;i++)
cin >> a[i];
build(1,n,1);
while(t--){
int x,y;
cin >> x >> y;
if(x>y) swap(x,y);
int ans = query(x,y,1,n,1).ans;
if(ans < 0) ans = 0;
cout << ans << '\n';
}
return 0;
}
```
-->
這題目有要求修改線段的話
更新的函數就和一般線段樹寫法差不多
```cpp=
void update(int pos,int val,int l,int r,int rt){
if(l == r){
tree[rt].lm = tree[rt].rm = tree[rt].sum = tree[rt].ans = val;
return;
}
int mid=(l+r)>>1;
if(pos<=mid)
update(pos,val,l,mid,rt<<1);
else
update(pos,val,mid+1,r,rt<<1|1);
tree[rt] = pull(tree[rt<<1],tree[rt<<1|1]);
}
```