前缀和
一维前缀和
s u m [ i ] = ∑ j = 1 i a [ i ] sum[i]= \sum_{j=1}^{i} a[i] sum[i]=j=1∑ia[i]
二维前缀和
s
u
m
[
i
]
[
j
]
=
s
u
m
[
i
−
1
]
[
j
]
+
s
u
m
[
i
]
[
j
−
1
]
−
s
u
m
[
i
−
1
]
[
j
−
1
]
+
a
[
i
]
[
j
]
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]
sum[i][j]=sum[i−1][j]+sum[i][j−1]−sum[i−1][j−1]+a[i][j]
差分
原 数 组 a [ ] , 差 分 数 组 b [ ] b [ 1 ] = a [ 1 ] , b [ i ] = a [ i ] − a [ i − 1 ] ( 2 ⩽ i ⩽ n ) 原数组a[],差分数组b[] \\ b[1]=a[1],b[i]=a[i]-a[i-1] (2\leqslant i \leqslant n) 原数组a[],差分数组b[]b[1]=a[1],b[i]=a[i]−a[i−1](2⩽i⩽n)
题目描述
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。
输入描述:
第一行有两个整数:L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
输出描述:
包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
示例1
输入
500 3
150 300
100 200
470 471
输出
298
备注:
对于20%的数据,区域之间没有重合的部分;
对于其它的数据,区域之间有重合的情况。
分析
考虑暴力的方法,定义一个标记数组,每读入一个区间,将区间内所有点标记,最后遍历整个数组,记录没有标记过的点的个数,时间复杂度O(L*M),对于本题的数据范围,暴力的做法可以通过,代码可以参考https://blog.csdn.net/weixin_46155777/article/details/104775969
但是对于更大的数据范围:1≤L≤100000,1≤M≤100000,就不能采用暴力的方法,需要用到差分。先把每个点都标记为1,然后求差分数组,对读入的每一组区间(l,r),只需做a[l]–,a[r+1]++,对区间整体减1,区间内相邻两个数的差值不变,但是要考虑到对区间右端点右边位置值的影响,a[r+1]++,时间复杂度O(L+M),下面是差分的代码
AC代码
#include <iostream>
using namespace std;
const int N=1e4+10;
int a[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=0;i<=n;i++) a[i]=1;
for(int i=n;i>0;i--)
{
a[i]=a[i]-a[i-1];
}
int flag=1;
while(m--)
{
int l,r;
cin>>l>>r;
a[l]-=1;
a[r+1]+=1;
}
int cnt=0;
if(a[0]>0) cnt++;
for(int i=1;i<=n;i++)
{
a[i]+=a[i-1];
if(a[i]>0) cnt++;
}
cout<<cnt<<endl;
return 0;
}
注意
1.建立差分数组的两个for循环可以替换为 a[0]=1;
2.对于更大的数据范围:1≤L≤109,1≤M≤100000,就不能采用上述差分的方法,无法开109大的数组。观察发现L的范围很大,但实际涉及的点只有2*M个,可以采用离散化的方法,只考虑点的相对位置,也用到了差分,代码如下
#include <bits/stdc++.h>
using namespace std;
const int N=105;
struct node{
int pos;
int num;
}p[2*N];
int n,m;
int tot;
bool cmp(const node&a,const node&b)
{
return a.pos<b.pos;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
p[++tot].pos=l;
p[tot].num=1;
p[++tot].pos=r+1;
p[tot].num=-1;
}
sort(p+1,p+tot+1,cmp);
int cnt=0,sum=0;
for(int i=1;i<=tot;i++)
{
sum+=p[i].num;
if(sum==1&&p[i].num==1)
{
cnt+=p[i].pos-p[i-1].pos;
}
}
cnt+=n-p[tot].pos+1;
cout<<cnt<<endl;
return 0;
}
离散化实例https://blog.csdn.net/weixin_46155777/article/details/113732214
题目描述
存在n个数,每次操作可以任选一个区间使得区间内的所有数字减一。问最少多少次操作,可以让所有数都变成1。
数据保证一定有解。
输入描述:
输入t,代表有t组数据。每组数据输入n,代表有n个数。接下来一行输入n个数,数字大小小于1e6。(t<=1000,n<1e5,∑n < 1e6)
输出描述:
每组数据输出一个整数代表最少需要操作的次数。
示例1
输入
1
6
1 3 5 2 7 1
输出
9
分析
差分,所有数都变成1,对应的差分数组是,首项是1,其他项为0,因此只需对原数组求差分数组,统计所有正数的总和,最终答案就是正数之和减1
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010;
int a[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--)
{
ll ans=0;
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
for(int i=n-1;i>0;i--)
{
a[i]-=a[i-1];
if(a[i]>0) ans+=a[i];
}
ans+=a[0];
cout<<ans-1<<endl;
}
return 0;
}
注意
本题的答案不能是差分数组负数之和的相反数,当选择的区间包含最后一个数时,差分数组会执行a[n+1]++,但数组只有n个元素,所以只能统计所有正数的和
题目描述
张经理的公司的办公室长达100000米,从最左端开始每间隔1米都有一个工位(从第1米开始有工位),位于第i米的工位称为i号工位,且这些工位都在一条水平线上。他有n个员工,每个员工分别位于xi号工位上(不同员工可能位于同一个工位)。
现在张经理想把员工聚集在某两个工位上,他有q套方案(每套方案包含两个工位号,两个工位号可能相同),他想知道对于每套方案,所有员工都到达两个工位中的某一个所需走的最短路径之和是多少。
输入描述:
第一行输入两个正整数n, q
第二行输入n个正整数xi,分别代表第i个员工的工位
之后q行每行输入两个整数a,b,代表该套方案要求的两个集合位置
(1<=n,q,xi,a,b<=105)
输出描述:
对于每套方案,输出一个整数代表答案,每个答案独占一行。
示例1
输入
3 2
1 3 5
1 4
2 1
输出
2
4
分析
维护两个前缀和,sum1[]:位置n之前包括n的员工到位置0的距离之和,sum2[]:位置n之前包括n的员工总数。对于读入的两个集合位置l,r,假设l<=r,mid=(l+r)>>1,位置<=mid的应该在l处集合,其他在r处集合,这样才能保证路径之和最短
AC代码
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int a[N];
int sum1[N];
int sum2[N];
int cnt[N];
int n,q;
int main()
{
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>a[i];
cnt[a[i]]++;
}
for(int i=1;i<N;i++)
{
sum1[i]=sum1[i-1]+cnt[i]*i;
sum2[i]=sum2[i-1]+cnt[i];
}
while(q--)
{
int l,r;
cin>>l>>r;
if(l>r) swap(l,r);
int mid=(l+r)>>1;
int ans=0;
ans+=sum2[l-1]*l-sum1[l-1];
ans+=(sum1[mid]-sum1[l])-(sum2[mid]-sum2[l])*l;
ans+=(sum2[r-1]-sum2[mid])*r-(sum1[r-1]-sum1[mid]);
ans+=(sum1[100000]-sum1[r])-(sum2[100000]-sum2[r])*r;
cout<<ans<<endl;
}
return 0;
}
题目描述
一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标。
现在地图上有n(N ≤ 10000)个目标,用整数Xi,Yi(其值在[0,5000])表示目标在地图上的位置,每个目标都有一个价值。
激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。
若目标位于爆破正方形的边上,该目标将不会被摧毁。
输入描述
输入文件的第一行为正整数n和正整数R,接下来的n行每行有3个正整数,分别表示 xi,yi ,vi 。
输出描述
输出文件仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过32767)。
示例1
输入
2 1
0 0 1
1 1 1
输出
1
分析
二维前缀和,先求出每个点的二维前缀和,再遍历所有边长为R的正方形,记录炸毁目标的最大值。
AC代码
#include <bits/stdc++.h>
using namespace std;
const int N=5010;
int a[N][N];
int f[N][N];
int r,c;
int main()
{
int n,R;
cin>>n>>R;
r=c=R;
while(n--)
{
int x,y,v;
cin>>x>>y>>v;
x++,y++;
a[x][y]=v;
r=max(r,x);
c=max(c,y);
}
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];
}
}
int ans=0;
for(int i=R;i<=r;i++)
{
for(int j=R;j<=c;j++)
{
ans=max(ans,f[i][j]-f[i-R][j]-f[i][j-R]+f[i-R][j-R]);
}
}
cout<<ans<<endl;
return 0;
}
注意
1.r,c初始值为R
2.在本题中是假设每个点处于格子的中央,将点当作格子处理