維護區間加等差數列的求和問題


這里直接以luogu上的一道了例題為例:https://www.luogu.com.cn/problem/P1438

 

題目描述

維護一個數列{a[i]},支持兩種操作:

1、1 L R K D:給出一個長度等於R-L+1的等差數列,首項為K,公差為D,並將它對應加到a[L]~a[R]的每一個數上。即:令a[L]=a[L]+K,a[L+1]=a[L+1]+K+D,

a[L+2]=a[L+2]+K+2D……a[R]=a[R]+K+(R-L)D。

2、2 P:詢問序列的第P個數的值a[P]。

輸入格式

第一行兩個整數數n,m,表示數列長度和操作個數。

第二行n個整數,第i個數表示a[i](i=1,2,3…,n)。

接下來的m行,表示m個操作,有兩種形式:

1 L R K D

2 P 字母意義見描述(L≤R)。

輸出格式

對於每個詢問,輸出答案,每個答案占一行。

輸入輸出樣例

輸入 #1
5 2
1 2 3 4 5
1 2 4 1 2
2 3
輸出 #1
6

說明/提示

數據規模:

0≤n,m≤100000

|a[i]|,|K|,|D|≤200

 

思路:

設原數組為a,考慮對a進行差分,對於差分數組d,a[p] = d[1] + d[2] + ... + d[p],而對a[l]~a[r]加上等差數列的值,在數組d上的修改為:d[l] = a[l] - a[l - 1],a[l]增加k,則d[l]增加了k。而d[l + 1] = a[l + 1] - a[l],a[l + 1]增加k + d,a[l]增加k,d[l + 1]增加d,同理對於i屬於[l + 1, r],d[i]都增加了d。最后d[r + 1] = a[r + 1] - a[r],a[r + 1]不變而a[r]增加了k + (r - l)d,故d[r + 1]增加了(l - r)d - k

到這里發現,對於a的操作,變換到d上,實際就成了區間求和/修改,a的更新操作,等價於d[l]和d[r + 1]更新以及d[l + 1]~d[r]區間更新。而得到a[p]的操作,相當於d[1]到d[p]區間求和。

那么我們就很容易通過線段樹來做了

 

  1 #include <iostream>
  2 #include <algorithm>
  3 #include <string>
  4 #include <string.h>
  5 #include <vector>
  6 #include <map>
  7 #include <stack>
  8 #include <set>
  9 #include <queue>
 10 #include <math.h>
 11 #include <cstdio>
 12 #include <iomanip>
 13 #include <time.h>
 14 
 15 #define LL long long
 16 #define INF 0x3f3f3f3f
 17 #define ls nod<<1
 18 #define rs (nod<<1)+1
 19 
 20 using namespace std;
 21 
 22 const int maxn = 1e5 + 10;
 23 const LL mod = 1e9 + 7;
 24 
 25 
 26 struct segment_tree {
 27     LL val;
 28     LL lazy;
 29 }tree[maxn<<2];
 30 
 31 LL a[maxn];
 32 
 33 void build (int l,int r,int nod) {
 34     tree[nod].lazy = 0;
 35     if (l == r) {
 36         tree[nod].val = a[l];
 37         return ;
 38     }
 39     int mid = (l + r) >> 1;
 40     build(l,mid,ls);
 41     build(mid+1,r,rs);
 42     tree[nod].val = tree[ls].val + tree[rs].val;
 43 }
 44 
 45 void pushdown(int l,int r,int nod) {
 46     int mid = (l + r) >> 1;
 47     tree[ls].lazy += tree[nod].lazy;
 48     tree[rs].lazy += tree[nod].lazy;
 49     tree[ls].val += (mid-l+1)*tree[nod].lazy;
 50     tree[rs].val += (r-mid)*tree[nod].lazy;
 51     tree[nod].lazy = 0;
 52 }
 53 
 54 void modify(int l,int r,int ql,int qr,int v,int nod) {
 55     if (ql <= l && qr >= r) {
 56         tree[nod].lazy += v;
 57         tree[nod].val += (r-l+1)*v;
 58         return ;
 59     }
 60     if (tree[nod].lazy)
 61         pushdown(l,r,nod);
 62     int mid = (l + r) >> 1;
 63     if (ql <= mid)
 64         modify(l,mid,ql,qr,v,ls);
 65     if (qr > mid)
 66         modify(mid+1,r,ql,qr,v,rs);
 67     tree[nod].val = tree[ls].val + tree[rs].val;
 68 }
 69 
 70 LL query(int l,int r,int ql,int qr,int nod) {
 71     if (ql <= l && qr >= r)
 72         return tree[nod].val;
 73     int mid = (l + r ) >> 1;
 74     if (tree[nod].lazy)
 75         pushdown(l,r,nod);
 76     LL cnt = 0;
 77     if (ql <= mid)
 78         cnt +=query(l,mid,ql,qr,ls);
 79     if (qr > mid)
 80         cnt +=query(mid+1,r,ql,qr,rs);
 81     return cnt;
 82 }
 83 LL dat[maxn];
 84 int main() {
 85     int n,m;
 86     scanf("%d%d",&n,&m);
 87     for (int i = 1;i <= n;i++) {
 88         scanf("%lld",&dat[i]);
 89     }
 90     for(int i=1;i<=n;i++)
 91         a[i]=dat[i]-dat[i-1];
 92     build(1,n,1);
 93     while (m--) {
 94         int opt,l,r,k,d;
 95         scanf("%d",&opt);
 96         if (opt == 1) {
 97             scanf("%d%d%d%d",&l,&r,&k,&d);
 98             modify(1,n,l,l,k,1);
 99             if (l+1 <= r)
100                 modify(1,n,l+1,r,d,1);
101             if (r+1 <= n)
102                 modify(1,n,r+1,r+1,-(k+d*(r-l)),1);
103         }
104         else {
105             scanf("%d",&l);
106             printf("%d\n",query(1,n,1,l,1));
107         }
108     }
109     return 0;
110 }
Ackerman

 

 

當然我們還可以進一步思考,進行數學推導

對d再次差分得到數組e:
e[1] = d[1] = a[1]
e[2] = d[2] - d[1] = (a[2] - a[1]) - a[1] = a[2] - 2a[1]
e[3] = d[3] - d[2] = (a[3] - a[2]) - (a[2] - a[1]) = a[3] - 2a[2] + a[1]
...
e[i] = d[i] - d[i - 1] = (a[i] - a[i - 1]) - (a[i - 1] - a[i - 2])
= a[i] - 2a[i - 1] + a[i - 2] ...

 

由之前的概述已知,對應到d為:

  • d[l] 增加 k
  • d[i] 增加 d (i屬於[l + 1, r]),即d[l + 1]~d[r]區間增加d
  • d[r + 1] 增加 (l - r)*d - k

對應到e為: d[l]增加k導致e[l]增加k,e[l + 1]增加 -k
d[l + 1]~d[r]區間增加d,導致e[l + 1]增加d,e[r + 1]增加 -d,而e[l + 2]到e[r]這一段由於抵消而不變化
d[r + 1]增加(l - r)*d - k導致e[r + 1]增加(l - r)*d - k,e[r + 2]增加 k - (l - r)*d
綜上,a的更新操作對應到e為:

    • e[l] 增加 k
    • e[l + 1]增加 d- k
    • e[r + 1] 增加 (l - r - 1)*d - k
    • e[r + 2] 增加 k - (l - r)*d

 

所以我們也可以通過二次差分數組再求兩次前綴和就可以得到最后結果

算法模版:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100005;
int n,m,d2[MAXN],l,r,a,k;
void add(int l,int r,int a,int k)
{
    d2[l]+=a;
    d2[l+1]+=k-a;
    d2[r+1]-=(r-l+1)*k+a;
    d2[r+2]-=(l-r)*k-a;
}
void pre_sum()
{
    for(int i=1;i<=n;++i)
    {
        d2[i]+=d2[i-1];
    }
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        scanf("%d %d %d %d",&l,&r,&a,&k);
        add(l,r,a,k);
    }
    pre_sum();
    pre_sum();
    for(int i=1;i<=n;++i)
    {
        printf("%d%c",d2[i],i==n?'\n':' ');
    }
    return 0;
}

 

 

其實區間➕多項式的問題都可以轉化為差分數組,然后對差分數組進行修改,最后再求前綴和從而解決

數學證明:https://blog.nowcoder.net/n/b0401b709aa540f0af78dfc8e66813fb

 

維護前綴和的前綴和

https://blog.nowcoder.net/n/bb352f0f59ea4509b0a7fc15b11fa5a8


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM