前缀和与差分

一维前缀和

从数组第一个开始累加

1
s[i]=s[i-1]+a[i]

求区间[l,r]的和,O(1)复杂度 sum=s[r]-s[l-1]

二维前缀和

递推 s[i][j]=s[i][j]-s[i-1][j]-s[i][j-1]+s[i-1][j-1]

例题:激光炸弹

一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标。现在地图上有n(n≤10000)个目标,用整数xi,yi(0≤xi,yi≤5000)表示目标在地图上的位置,每个目标都有一个价值0<vi<100。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。**若目标位于爆破正方形的边上,该目标将不会被摧毁**


将点向右下移动一次就可解决边界不包括的点
给出一个5000×5000的矩阵,每个格子上都有权值,求用一个n×n的框最多能框住多少?

用前缀和来求n*n的框的价值
value=s[i][j]+s[i-r][j]+s[i][j-r]-s[i-r][j-r]);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn=5005;
int s[maxn][maxn];
int main(){
int n,r;
// memset(s,0,sizeof s);
freopen("a.txt","r",stdin);
cin>>n>>r;//n个数,正方形半径为r
// 没有注意到相同点会出现多次
// for(int i=0;i<n;i++){
// int a,b;
// cin>>a>>b;
// cin>>s[a+1][b+1];//向右下方移动1位
// }
for(int i=1;i<=n;i++){
int a,b,v;
cin>>a>>b>>v;
sum[a+1][b+1]+=v;
}
for(int i=1;i<=5001;i++){//第0默认为0
for(int j=1;j<=5001;j++){
s[i][j]=s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];//二维前缀和
}
}
int ans=0;
for(int i=r;i<=5001;i++){
for(int j=r;j<=5001;j++){
ans=max(ans,s[i][j]-s[i-r][j]-s[i][j-r]+s[i-r][j-r]);//遍历所有存在的子区域
}
}
cout<<ans;
return 0;
}

差分

差分与前缀和是一种互逆的运算

数组| 2 |1 | 4 |6 |5
|— |—|— | — | — | — |
前缀和| 2| 3 |7 | 13 | 18
差分 |2| -1 |3 |2 | -1

  • 差分前缀和是原数组
  • 前缀和差分也是原数组

    结论题

    IncDec sequence(inc序列)

    给定一个长度为n(n<=$10^5$)的数列,每次选择区间 [l,r],使区间内的数加1或减1.
    求最少需要多少次操作使的数列中所有的数都一样,并且数列可能有几种?

    区间操作放在差分数组中就是改变2个数组的值,目标是,差分数组$b_2$~$b_n$都为0,且数列最终由n个$b_1$组成的,那么不同的$b_1$出现的次数代表了不同的数列

    四种操作
  • 从$b_2$~$b_n$中选两个分别+1,-1,或者-1,+1,减少正负数,使差分数组向0移动, (更快接近结果)
  • 选 $b_1$和$b_j$ (2<=j<=n),改变差分数组中某一个数($b_1$改变影响最终不同数列的次数,不影响操作的次数),
  • 同理,选$b_j$和$b_{n+1}$,$b_{n+1}$代表一直改变到原数列的最后一个,所以还是改变了差分数组的一个值
  • 改变$b_1$和$b_{n+1}$,原数组全部+,- 1,没有实际意义
  1. 设$b_2$~$b_n$正数总和p,负数绝对值总和q,(原则是走最少),第一步走min(q,p),剩下|q-p|个正数或者负数,
  2. 剩下的|p-q|执行2,3操作,那么恰好为|p-q|次
    操作次数
    1
    ans=min(q,p)+|q-p|
    1
    min(p,q)+|q-p|=max(q,p)

    证明

  3. p>qmin(p,q)=q |q-p|=p-q 得到ans=q+p-q=p
    p=max(p,q)
  4. 同理 p<q
    数列个数
    $b_1$一开始为1种,若2,3操作都为2操作,最多可能有|p-q|+1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include<iostream>
    #include<cmath>
    using namespace std;
    const int maxn=pow(10,5)+1;
    int a[maxn];
    int b[maxn];
    int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
    cin>>a[i];
    b[i]=a[i];
    b[i]-=b[i-1];
    }
    // for(int i=1;i<=n;i++)
    // cout<<b[i]<< ' ';
    int sum_1=0,sum_2=0;
    for(int i=2;i<=n;i++){//b2开始统计正负
    if(b[i]>0) sum_1+=b[i];
    else sum_2+=b[i];//0无所谓
    }
    cout<<"操作次数"<<max(sum_1,sum_2);
    cout<<"\n可能的不同数列个数"<<1+abs(sum_1-sum_2);
    return 0;
    }

    Tallest Cow 最高奶牛 POJ3263

大意:N头牛站成一行,当且仅当2头牛之间所有牛都比它们矮,2头牛能相互看见,一直第p头牛最高,高为H,剩余N-1头牛不知道,给出m对关系(2头牛能够相互看见),求每头牛的最大可能身高? (1<=n,m<=$10^4$,1<=h<=$10^6$)

思路:因为i,j,两头牛能够相互看见,那么(i-1)~(j-1)之间的所有牛的身高比他们小,假设所有牛的身高为0,那么(i-1)~(j-1)之间牛的身高都-1,即b[i+1]-=1 b[j]+=1 最后通过前缀和求出原数组,每头牛可能的最高身高=H+a[i]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostream>
#include<map>
using namespace std;
const int maxn=10005;
int a[maxn];
int b[maxn];
map<pair<int,int>,bool> e;
int main(){
int n,p,h,m;
cin>>n>>p>>h>>m;
for(int i=1;i<=m;i++){
int k1,k2;
cin>>k1>>k2;
if(k1>k2) swap(k1,k2);
if( e[make_pair(k1,k2)] == true ) continue;
else{
e[make_pair(k1,k2)]=true;
b[k1+1]--;
b[k2]++;
}
}
for(int i=1;i<=n;i++){
a[i]=a[i-1]+b[i];
cout<<a[i]+h<<endl;
}
return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!