線段樹進階
- 線段樹進階
- 線段樹+貪心/線段樹+排序
- 例題:
- 1.洛谷P1607 Fair Shuttle G
- 2.洛谷P1937 Barn Allocation G
- 3.洛谷P1972 HH的項鍊
- 例題:
- 線段樹+雙指標
- 例題:
- 1.洛谷P1712 區間
- 例題:
- 線段樹+多個標記維護
- 例題:
- 1.洛谷P2572 序列操作
- 例題:
- 線段樹+二分
- 例題:
- 1.洛谷P4344 腦洞治療儀
- 2.洛谷P2824 排序
- 例題:
- 線段樹+數學
- 例題:
- 1.洛谷P5142 方差
- 例題:
- 線段樹+貪心/線段樹+排序
未更新完!!!!!!
線段樹板子:
#define lc u<<1
#define rc u<<1|1
const int N = 200005;
struct tree{
int l,r,su,tag;
}tr[N << 2];
void push_up(tree &u,tree l,tree r){
}
void build(int u,int l,int r){
tr[u] = {l,r,a[l],0};
if(l==r) return;
int mid = l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
push_up(tr[u],tr[lc],tr[rc]);
}
void calc(int u,int op){
tree& t = tr[u];
if(op==1){
}else{
}
}
void push_down(int u){
if(tr[u].tag == ?) calc(lc,?),calc(rc,?);
if(tr[u].tag == ?) calc(lc,?),calc(rc,?);
tr[u].tag = 0;
}
void update(int u,int l,int r,int op){
if(tr[u].l > r || tr[u].r < l) return;
if(tr[u].l >= l && tr[u].r <= r){
calc(u,op);
return;
}
push_down(u);
if(l > tr[lc].r){
update(rc,l,r,op);
push_up(tr[u],tr[lc],tr[rc]);
return;
}
if(r < tr[rc].l){
update(lc,l,r,op);
push_up(tr[u],tr[lc],tr[rc]);
return;
}
update(lc,l,r,op);
update(rc,l,r,op);
push_up(tr[u],tr[lc],tr[rc]);
}
//合併查詢
T quary(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return {} ;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u];
}
push_down(u);
if(l > tr[lc].r) return quary(rc,l,r); //在右半部分
if(r < tr[rc].l) return quary(lc,l,r); //在左半部分
T t;
push_up(t,quary(lc,l,r),quary(rc,l,r));//兩個部分各佔一點
return t;
}
線段樹+貪心/線段樹+排序
例題:
1.洛谷P1607 Fair Shuttle G
連結:
[P1607 USACO09FEB] Fair Shuttle G - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
思路:
線段覆蓋貪心問題,觀察對比發現按 r時間順序排序得到的答案明顯更大,維護區間最大值即可,注意要做修改操作,因為中途奶牛上車會影響最大值
程式碼:
#define lc u<<1
#define rc u<<1|1
const int N = 20005;
struct node
{
int l,r,w;
bool operator<(const node &n1)const{
return r < n1.r;
};
};
struct tree{
int l,r,mx,add;
}tr[N << 2];
void push_up(int u){
tr[u].mx = max(tr[lc].mx,tr[rc].mx);
}
void push_down(int u){
tr[lc].add += tr[u].add;
tr[rc].add += tr[u].add;
tr[lc].mx += tr[u].add;
tr[rc].mx += tr[u].add;
tr[u].add = 0;
}
void build(int u,int l,int r){
tr[u] = {l,r,0,0};
if(l==r) return;
int mid = l + r >> 1;
build(lc,l,mid);
build(rc,mid + 1,r);
}
void update(int u,int l,int r,int v){
if(tr[u].l > r || tr[u].r < l) return;
if(tr[u].l >= l && tr[u].r <= r){
tr[u].mx += v;
tr[u].add += v;
return;
}
push_down(u);
update(lc,l,r,v);
update(rc,l,r,v);
push_up(u);
}
int quary(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].mx;
}
push_down(u);
return max(quary(lc,l,r),quary(rc,l,r));
}
int n,m,c;
vector<node> a;
void solve(){
cin >> n >> m >> c;
build(1,1,m + 1);
for(int i = 1;i<=n;i++){
int l,r,w;
cin >> l >> r >> w;
a.push_back({l,r,w});
}
sort(a.begin(),a.end());
int ans = 0;
for(int i = 0;i < n;i++){
int l = a[i].l, r= a[i].r, w = a[i].w;
int mx = quary(1,l,r - 1);
int on = min(c - mx,w);
ans+=on;
update(1,l,r - 1,on);
}
cout << ans << endl;
}
2.洛谷P1937 Barn Allocation G
連結:
[P1937 USACO10MAR] Barn Allocation G - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
思路:
線段覆蓋貪心問題,觀察對比發現按 r時間順序排序得到的答案明顯更優,對開始的陣列建立線段樹,表示當前各個區間的容量最小值,判斷1頭牛能否加入一個區間,加入則ans++,同時更新區間將區間容量-1;
程式碼:
#define lc u<<1
#define rc u<<1|1
const int N = 100005;
int a[N];
struct node{
int l,r;
bool operator<(const node & n1) const{
return r < n1.r;
}
};
struct tree{
int l,r,mi,add;
}tr[N << 2];
void push_up(int u){
tr[u].mi = min(tr[lc].mi,tr[rc].mi);
}
void push_down(int u){
tr[lc].add += tr[u].add;
tr[rc].add += tr[u].add;
tr[lc].mi -= tr[u].add;
tr[rc].mi -= tr[u].add;
tr[u].add = 0;
}
void build(int u,int l,int r){
tr[u] = {l,r,0x3f3f3f3f,0};
if(l==r){
tr[u] = {l,r,a[l],0};
return;
}
int mid = l + r >> 1;
build(lc,l,mid);
build(rc,mid + 1,r);
push_up(u);
}
void update(int u,int l,int r,int v){
if(tr[u].l > r || tr[u].r < l) return;
if(tr[u].l >= l && tr[u].r <= r){
tr[u].mi -= v;
tr[u].add += v;
return;
}
push_down(u);
update(lc,l,r,v);
update(rc,l,r,v);
push_up(u);
}
int quary(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0x3f3f3f3f;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].mi;
}
push_down(u);
return min(quary(lc,l,r),quary(rc,l,r));
}
int n,m;
void solve(){
cin >> n >> m;
for(int i = 1;i <= n;i++){
cin >> a[i];
}
build(1,1,n);
vector<node> a;
for(int i = 1;i<=m;i++){
int l,r;
cin >>l >> r;
a.push_back({l,r});
}
sort(a.begin(),a.end());
int ans = 0;
for(int i = 0; i < m;i++){
int l = a[i].l, r = a[i].r;
int mi = quary(1,l,r);
if(mi <= 0){
continue;
}
ans++;
update(1,l,r,1);
}
cout << ans << endl;
}
3.洛谷P1972 HH的項鍊
連結:
[P1972 SDOI2009] HH的項鍊 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
思路:
我們將線上詢問改為離線,然後對r進行排序,然後順序遍歷陣列,如果碰到之前出現過的數,把之前出現的最後一次的下標,單點修改-1,把當前下標+1,對每個r開個桶,然後線段樹統計答案即可
程式碼:
#define lc u<<1
#define rc u<<1|1
const int N = 1000005;
struct node{
int l,r,id;
bool operator<(const node & n1) const{
return r < n1.r;
}
};
struct tree{
int l,r,su;
}tr[N << 2];
void push_up(int u){
tr[u].su = tr[lc].su+tr[rc].su;
}
void build(int u,int l,int r){
tr[u] = {l,r,0};
if(l==r) return;
int mid = l + r >> 1;
build(lc,l,mid);
build(rc,mid + 1,r);
}
void update(int u,int x,int v){
if(tr[u].l > x || tr[u].r < x) return;
if(tr[u].l == x && tr[u].r == x){
tr[u].su += v;
return;
}
update(lc,x,v);
update(rc,x,v);
push_up(u);
}
int quary(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].su;
}
return quary(lc,l,r) + quary(rc,l,r);
}
int n,m;
int a[N],last[N],ans[N];
vector<node> v[N];
vector<node> qs;
void solve(){
cin >> n;
build(1,1,n);
for(int i = 1;i<=n;i++){
cin >> a[i];
}
cin >> m;
for(int i = 1; i <= m;i++){
int l,r;
cin >> l >> r;
v[r].push_back({l,r,i});
sort(v[r].begin(),v[r].end());
}
for(int i = 1;i<=n;i++){
if(last[a[i]]){
update(1,last[a[i]],-1);
}
last[a[i]] = i;
update(1,i,1);
for(auto &p:v[i]){
ans[p.id] = quary(1,p.l,p.r);
}
}
for(int i =1;i<=m;i++){
cout << ans[i] << endl;
}
}
線段樹+雙指標
例題:
1.洛谷P1712 區間
連結:
[P1712 NOI2016] 區間 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)https://www.luogu.com.cn/problem/P1937)
思路:
對每個區間記錄下來並且按區間長度排序,進行同向雙指標,看陣列區間內是否有一段長度(即最大長度,超過m,有就右指標不動,左指標不斷右移,不斷更新答案最小值)
程式碼:
#define lc u<<1
#define rc u<<1|1
const int N = 500005;
struct node{
int l,r,len;
bool operator<(const node & n1) const{
return len < n1.len;
}
};
struct tree{
int l,r,ma,add;
}tr[N*2 << 2];
void push_up(int u){
tr[u].ma = max(tr[lc].ma,tr[rc].ma);
}
void push_down(int u){
tr[lc].add += tr[u].add;
tr[rc].add += tr[u].add;
tr[lc].ma += tr[u].add;
tr[rc].ma += tr[u].add;
tr[u].add = 0;
}
void build(int u,int l,int r){
tr[u] = {l,r,0,0};
if(l==r) return;
int mid = l + r >> 1;
build(lc,l,mid);
build(rc,mid + 1,r);
push_up(u);
}
void update(int u,int l,int r,int v){
if(tr[u].l > r || tr[u].r < l) return;
if(tr[u].l >= l && tr[u].r <= r){
tr[u].ma += v;
tr[u].add += v;
return;
}
push_down(u);
update(lc,l,r,v);
update(rc,l,r,v);
push_up(u);
}
int quary(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].ma;
}
push_down(u);
return max(quary(lc,l,r),quary(rc,l,r));
}
int n,m;
void solve(){
cin >> n >> m;
vector<node> a;
vector<int> v;
for(int i = 1;i<=n;i++){
int l,r;
cin >>l >> r;
a.push_back({l,r,r-l});
v.push_back(l);
v.push_back(r);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
int sz = v.size();
sort(a.begin(),a.end());
build(1,1,2*n);
for(int i = 0;i < n;i++){
a[i].l = lower_bound(v.begin(),v.end(),a[i].l) - v.begin() + 1;
a[i].r = lower_bound(v.begin(),v.end(),a[i].r) - v.begin() + 1;
}
int j = 0;
int ans = 0x3f3f3f3f;
for(int i = 0;i < n;i++){
int l = a[i].l,r = a[i].r;
update(1,l,r,1);
while(j <= i && tr[1].ma == m){
update(1,a[j].l,a[j].r,-1);
ans=min(ans,a[i].len - a[j].len);
j++;
}
}
if(ans==0x3f3f3f3f){
cout << -1 <<endl;
return;
}
cout << ans << endl;
}
線段樹+多個標記維護
例題:
1.洛谷P2572 序列操作
連結:
[P2572 SCOI2010] 序列操作 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
思路:
維護2個懶標記,和多個普通標記,不只是要加入1的連續,還要加入0的連續,方便反轉後的更改
考慮更新lazy,優先順序肯定是覆蓋操作大,然後才是反轉
程式碼:
#define lc u<<1
#define rc u<<1|1
int a[N];
struct tree{
int l,r;
int s0,l0,r0,m0; //0的個數,左最長,右最長,總最長
int s1,l1,r1,m1; //1的個數,左最長,右最長,總最長
int len;
int rev,tag; //rev 0/1是否取反, tag -1無標記/ 0全部為0 / 1全部為1
}tr[N<<2];
void push_up(tree& t,tree p1,tree p2){
t.s0 = p1.s0 + p2.s0;
t.l0 = p1.s1 ? p1.l0 : p1.s0 + p2.l0;
t.r0 = p2.s1 ? p2.r0 : p2.s0 + p1.r0;
t.m0 = max(p1.r0 + p2.l0,max(p1.m0,p2.m0));
t.s1 = p1.s1 + p2.s1;
t.l1 = p1.s0 ? p1.l1 : p1.s1 + p2.l1;
t.r1 = p2.s0 ? p2.r1 : p2.s1 + p1.r1;
t.m1 = max(p1.r1 + p2.l1,max(p1.m1,p2.m1));
}
void calc(int u,int op){
tree& t = tr[u];
if(op==0){
t.s0 = t.l0 = t.r0 = t.m0 = t.len;
t.s1 = t.l1 = t.r1 = t.m1 = 0;
t.tag=0;
t.rev=0;
}
if(op==1){
t.s0 = t.l0 = t.r0 = t.m0 = 0;
t.s1 = t.l1 = t.r1 = t.m1 = t.len;
t.tag=1;
t.rev=0;
}
if(op==2){
swap(t.s0,t.s1);
swap(t.l0,t.l1);
swap(t.r0,t.r1);
swap(t.m0,t.m1);
t.rev^=1;
}
}
void push_down(int u){
tree& t = tr[u];
if(t.tag==0) calc(lc,0),calc(rc,0);
if(t.tag==1) calc(lc,1),calc(rc,1);
if(t.rev) calc(lc,2),calc(rc,2);
t.tag = -1; t.rev = 0;
}
void build(int u,int l,int r){
int t = a[l];
//如果是t=0 t^1 = 1; 0 的個數 正好是1,1的個數正好是0,反之也合理
tr[u] = {l,r,t^1,t^1,t^1,t^1,t,t,t,t,r-l+1,0,-1};
if(l==r) return;
int mid = l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
push_up(tr[u],tr[lc],tr[rc]);
}
void update(int u,int l,int r,int op){
if(tr[u].l > r || tr[u].r < l) return;
if(tr[u].l >= l && tr[u].r <= r){
calc(u,op);
return;
}
push_down(u);
update(lc,l,r,op);
update(rc,l,r,op);
push_up(tr[u],tr[lc],tr[rc]);
}
tree qy(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return { };
if(tr[u].l >= l && tr[u].r <= r){
return tr[u];
}
push_down(u);
tree ne;
push_up(ne,qy(lc,l,r),qy(rc,l,r));
return ne;
}
void solve(){
int n,m;
cin >> n >> m;
for(int i = 1;i<=n;i++) cin >>a[i];
build(1,1,n);
for(int i = 1;i<=m;i++){
int op,l,r;
cin >> op>>l>>r;
++l,++r;
if(op<3){
update(1,l,r,op);
}else if(op==3){
tree ne = qy(1,l,r);
printf("%d\n",ne.s1);
}else if(op==4){
tree ne = qy(1,l,r);
printf("%d\n",ne.m1);
}
}
}
線段樹+二分
例題:
1.洛谷P4344 腦洞治療儀
連結:
[P4344 SHOI2015] 腦洞治療儀 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
思路:
我們維護最長連續0長度,維護1的個數,然後對於前面要填充的區域,我們二分出一個最小合法區域\([l,x](x為我們二分的右邊界)\)在最後quary求長度合併時注意程式碼細節,有註釋;
程式碼:
#define lc u<<1
#define rc u<<1|1
int n,m;
struct tree{
int l,r,len,s1,l1,r1,m1,tag;
}tr[N<<2];
void push_up(tree &u,tree l,tree r){
u.s1 = l.s1+r.s1;
u.l1 = l.s1 ? l.l1 :l.len+r.l1 ;
u.r1 = r.s1 ? r.r1 : l.r1+r.len ;
u.m1 = max(max(l.m1,r.m1),l.r1+r.l1);
}
void build(int u,int l,int r){
tr[u] = {l,r,r-l+1,1,0,0,0,-1};
if(l==r) return;
int mid = l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
push_up(tr[u],tr[lc],tr[rc]);
}
void calc(int u,int op){
tree& t = tr[u];
if(op==1){
t.s1 = t.len;
t.l1 = t.r1 = t.m1 = 0;
t.tag = 1;
}else{
t.s1 = 0;
t.l1 = t.r1 = t.m1 = t.len;
t.tag = 0;
}
}
void push_down(int u){
if(tr[u].tag == 1) calc(lc,1),calc(rc,1);
if(tr[u].tag == 0) calc(lc,0),calc(rc,0);
tr[u].tag = -1;
}
//只有兩種操作,一種置0,一直置1
void update(int u,int l,int r,int op){
if(tr[u].l > r || tr[u].r < l) return;
if(tr[u].l >= l && tr[u].r <= r){
calc(u,op);
return;
}
push_down(u);
update(lc,l,r,op);
update(rc,l,r,op);
push_up(tr[u],tr[lc],tr[rc]);
}
int q1(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].s1;
}
push_down(u);
return q1(lc,l,r)+q1(rc,l,r);
}
int q0(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].len - tr[u].s1;
}
push_down(u);
return q0(lc,l,r)+q0(rc,l,r);
}
void work(int l0,int r0,int l1,int r1){
int x = q1(1,l0,r0);
update(1,l0,r0,0);
if(x==0) return;
int l = l1,r = r1,ans = -1;
while(l<=r){
int mid = l+r>>1;
if(q0(1,l1,mid) <= x){
l = mid + 1;
ans = mid;
}else{
r = mid - 1;
}
}
if(ans==-1) return;
update(1,l1,ans,1);
}
tree quary(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return { } ;
if(tr[u].l >= l && tr[u].r <= r){
return tr[u];
}
push_down(u);
if(l > tr[lc].r) return quary(rc,l,r); //在右半部分
if(r < tr[rc].l) return quary(lc,l,r); //在左半部分
tree t;
push_up(t,quary(lc,l,r),quary(rc,l,r));//兩個部分各佔一點
return t;
}
void solve(){
cin >> n >> m;
build(1,1,n);
while(m--){
int op,l,r;
cin >> op >> l >> r;
if(op==0) update(1,l,r,0);
if(op==1){
int l1,r1;
cin>>l1>>r1;
work(l,r,l1,r1);
}
if(op==2){
cout << quary(1,l,r).m1 << endl;
}
}
}
2.洛谷P2824 排序
連結:
[P2824 HEOI2016/TJOI2016] 排序 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
思路:
因為是排列,我們想到二分答案,對於一個假定答案x設定>=x的數等於1,小於等於x的數為0,那麼構造線段樹,可以極快的解決排序,問題然後每次check出來題目要求的idx位置是1那麼就左指標右移,為什麼?因為x能滿足,那麼1~x-1都能滿足,所以我們區間要去 x+1~r中試圖尋找答案
程式碼:
#define lc u<<1
#define rc u<<1|1
int n,m;
struct qs{
int op,l,r;
}q[N];
//我們二分一個數字,看操作完是否idx這個位置=1;
int a[N];
struct tree{
int l,r,su,len,tag;
}tr[N<<2];
void push_up(int u){
tr[u].su = tr[lc].su + tr[rc].su;
}
void build(int u,int l,int r,int x){
tr[u] = {l,r,(a[l]>=x),r-l+1,-1};
if(l==r)return;
int mid = (l + r) >>1;
build(lc,l,mid,x);
build(rc,mid+1,r,x);
push_up(u);
}
void calc(int u,int op){
if(op==0){
tr[u].su = 0;
}else{
tr[u].su = tr[u].len;
}
tr[u].tag = op;
}
void push_down(int u){
if(tr[u].tag==0) calc(lc,0),calc(rc,0);
if(tr[u].tag==1) calc(lc,1),calc(rc,1);
tr[u].tag = -1;
}
void update(int u,int l,int r,int op){
if(tr[u].l > r || tr[u].r < l) return;
if(tr[u].l >= l && tr[u].r <= r){
calc(u,op);
return;
}
push_down(u);
update(lc,l,r,op);
update(rc,l,r,op);
push_up(u);
}
int q1(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l>=l&&tr[u].r<=r){
return tr[u].su;
}
push_down(u);
return q1(lc,l,r) + q1(rc,l,r);
}
int idx;
bool check(int x){
build(1,1,n,x);
for(int i = 1;i <= m;i++){
int op = q[i].op,l = q[i].l,r = q[i].r;
if(op==1){
int cnt = r - l + 1;
int su = q1(1,l,r);
update(1,l,l + su - 1,1);
update(1,l + su,r,0);
}else{
int cnt = r - l + 1;
int su = q1(1,l,r);
update(1,l,r - su,0);
update(1,r - su + 1,r,1);
}
}
return q1(1,idx,idx)==1;
}
void work(){
int l = 1,r = n,ans = -1;
while(l<=r){
int mid = l + r >> 1;
if(check(mid)){
l = mid + 1;
ans = mid;
}else{
r = mid - 1;
}
}
cout << ans << endl;
}
void solve(){
cin >> n >> m;
for(int i =1;i<=n;i++) cin >> a[i];
for(int i = 1;i<=m;i++){
cin >> q[i].op >> q[i].l >> q[i].r;
}
cin >> idx;
work();
}
線段樹+數學
例題:
1.洛谷P5142 方差
連結:
P5142 區間方差 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)https://www.luogu.com.cn/problem/P4344)
思路:
題意:要求一個區間的方差\(\frac{1}{n}\sum_{i=1}^n(a_i - a)^2\) 其中a為區間平均值\(a = \frac{1}{n}\sum_{i=1}^{n}a_i\)
我們將公式化簡 為了方便觀看,我們將區間平均值設定為\(b\)
\(d = \frac{1}{n}\sum_{i = 1}^n(a_i - b)^2\)
\(=\frac{1}{n}\sum_{i = 1}^n(a_i^2 - 2a_ib + b^2)\)
\(=\frac{1}{n}(\sum_{i = 1}^na_i^2 - \sum_{i = 1}^n2a_ib +\sum_{i = 1}^n b^2)\)
\(=\frac{1}{n}(\sum_{i = 1}^na_i^2 - 2b\sum_{i = 1}^na_i +n b^2)\)
\(=\frac{1}{n}(\sum_{i = 1}^na_i^2 - 2bnb +nb^2)\)
\(=\frac{1}{n}(\sum_{i = 1}^na_i^2 - 2nb^2 +nb^2)\)
\(=\frac{1}{n}(\sum_{i = 1}^na_i^2 - nb^2)\)
\(=\frac{1}{n}\sum_{i = 1}^na_i^2 - b^2\)
線段樹維護區間和和區間平方和即可
程式碼:
#define lc u<<1
#define rc u<<1|1
const int N = 200005;
const int MOD = 1e9+7;
int n,m;
int b[N];
struct tree{
int l,r,s1,s2;
}tr[N<<2];
void push_up(int u){
tr[u].s1 = (tr[lc].s1 + tr[rc].s1)%MOD;
tr[u].s2 = (tr[lc].s2 + tr[rc].s2)%MOD;
}
void build(int u,int l,int r){
tr[u] = {l,r,b[l],b[l]*b[l]%MOD};
if(l==r) return;
int mid = l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
push_up(u);
}
void update(int u,int x,int v){
if(tr[u].l > x || tr[u].r < x) return;
if(tr[u].l==x&&tr[u].r==x){
tr[u].s1 = v;
tr[u].s2 = (v*v)%MOD;
return;
}
update(lc,x,v);
update(rc,x,v);
push_up(u);
}
int q1(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l>=l&&tr[u].r<=r){
return tr[u].s1;
}
return (q1(lc,l,r) + q1(rc,l,r))%MOD;
}
int q2(int u,int l,int r){
if(tr[u].l > r || tr[u].r < l) return 0;
if(tr[u].l>=l&&tr[u].r<=r){
return tr[u].s2;
}
return (q2(lc,l,r) + q2(rc,l,r))%MOD;
}
int ksm(int x,int n){
int res = 1;
while(n){
if(n&1) res = res * x % MOD;
x = x*x%MOD;
n>>=1;
}
return res;
}
void solve(){
cin >> n >> m;
for(int i = 1;i<=n;i++) cin >> b[i];
build(1,1,n);
while(m--){
int op,l,r;
cin >> op >> l >> r;
if(op==1){
update(1,l,r);
}else{
int s1 = q1(1,l,r);
int s2 = q2(1,l,r);
int len = r-l+1;
int iv = ksm(len,MOD-2)%MOD;
s2 = s2*iv%MOD;
s1 = s1*iv%MOD;
s1 = (s1*s1)%MOD;
int res = ((s2 - s1)%MOD + MOD)%MOD;
cout << res <<endl;
}
}
}