算法竞赛入门——前缀和差分

前缀和

一维前缀和

s u m [ i ] = ∑ j = 1 i a [ i ] sum[i]= \sum_{j=1}^{i} a[i] sum[i]=j=1ia[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[i1][j]+sum[i][j1]sum[i1][j1]+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[i1](2in)

校门外的树

题目描述

某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入描述:

第一行有两个整数:L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

扫描二维码关注公众号,回复: 12558888 查看本文章

输出描述:

包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。

示例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;
}

[HNOI2003]激光炸弹

题目描述

一种新型的激光炸弹,可以摧毁一个边长为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.在本题中是假设每个点处于格子的中央,将点当作格子处理

猜你喜欢

转载自blog.csdn.net/weixin_46155777/article/details/113703906
今日推荐