差分前缀和——出行计划

前缀和算法:是一种预处理,在之后的计算中直接应用前面已经算出的结果。一般在如下的情况可能会用到:给出n个数的序列,要求回答m次询问,每次询问下标l到r的和

如果这种情况采用暴力,一个一个的求,大概率会超时,我们可以预处理,把前i个位置的和算出来存储在数组里面,以便后面询问的时候直接使用。

下面要讲的差分前缀和是前缀和的一种特例,如果没有前缀和算法基础的同学,可以先去了解一下,会对本次内容有个更深的理解。下面来看一道题,来学习一下什么是差分前缀和:


出行计划(转自CSP认证)

问题描述

最近西西艾弗岛上出入各个场所都要持有一定时限内的核酸检测阴性证明。

具体来时,如果在 t 时刻做了核酸检测,则经过一段时间后可以得到核酸检测阴性证明。这里我们假定等待核酸检测结果需要 k 个单位时间,即在 t+k 时刻可以获得结果。如果一个场所要求持 24 个单位时间内核酸检测结果入内,那么凭上述的核酸检测结果,可以在第 t+k 时刻到第 t+k+23 时刻进入该场所。

小 C 按时间顺序列出接下来的 n 项出行计划,其中第 i 项(1≤i≤n)可以概括为:
ti 时刻进入某场所,该场所需持有 ci 个单位时间内的核酸检测结果入内,其中 0<ci≤2×105。

为了合理安排核酸检测时间,试根据小 C 的出行计划,回答如下查询:

  • 如果在 q 时刻做了核酸检测,有多少项出行计划的核酸检测要求可以得到满足?

这样的查询共有 m 个,分别为 q1,q2,⋯,qm;查询之间互不影响。

输入格式

输入的第一行包含空格分隔的三个正整数 n、m 和 k,分别表示出行计划数目、查询个数以及等待核酸检测结果所需时间。

接下来输入 n 行,其中每行包含用空格分隔的两个正整数 ti、ci,表示一项出行计划;出行计划按时间顺序给出,满足 0<t1≤t2≤⋯≤tn≤2×105。

最后输入 m 行,每行仅包含一个正整数 qi,表示一个查询。m 个查询亦按照时间顺序给出,满足 0<q1<q2<⋯<qm≤2×105。

输出格式

输出共 m 行,每行一个整数,表示对应查询的答案。

样例输入

6 2 10
5 24
10 24
11 24
34 24
35 24
35 48
1
2

样例输出

3
3

样例解释

时刻 1 做检测,可以满足第三、四、六项出行计划;

时刻 2 做检测,可以满足第四、五、六项出行计划。

子任务

40% 的测试数据满足 0<n,k≤1000、m=1;

70% 的测试数据满足 0<n,m,k≤1000;

全部的测试数据满足 0<n,m,k≤105。


本题很符合我们当前疫情的现实生活,题目不难理解。有个计划出行,当且仅在ti实施,在ti之后就不能出行了(要想出行该计划,核酸检测证明必须要在ti时间前出来),并且要持有ci时间的核酸检测阴性证明(计划出行时间和核酸检测阴性证明出来时间之间的差值要在ci时间之内)。

暴力模拟:

#include<iostream>
using namespace std;
struct plan
{
	int time;
	int last;
}; 
plan arr[200001];
int count[200001]={0};//记录可执行几个计划 
int main()
{
	int n=0, m=0, k=0;//n为出行计划数目,m为查询个数,k为等待时间
	scanf("%d%d%d", &n, &m, &k);
	int time=0, last=0;
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d", &arr[i].time, &arr[i].last);
	}
	int begin=0;
	for(int i=1; i<=m; i++)
	{
		int flag=0;
		scanf("%d", &begin);
		begin=begin+k;
	  	for(int j=n; j>=1; j--)
  		{
  			if(arr[j].time<begin)break;
  			else if(begin<=arr[j].time&&(begin+arr[j].last-1)>=arr[j].time)flag++;
  		}
  		count[i]=flag; 
 	}
 	for(int i=1; i<=m; i++)
 	{
  		printf("%d\n", count[i]);
 	}
	return 0;
}

设置一个plan结构体,记录该计划的出行时间和需要核酸检测阴性证明时间,询问的时间一一遍历,记录可出行计划数,存储下来一起输出。时间复杂度为O(n^2),因为两个for嵌套循环,运行时间超时,只能通过70%测试点,70分。

差分前缀和:

ac代码:

#include<iostream>
using namespace std;
int arr[200001]={0};
int count[200001]={0};
int main()
{
	int n=0, m=0, k=0;//n为出行计划数目,m为查询个数,k为等待时间
	scanf("%d%d%d", &n, &m, &k);
	int time=0, last=0, max_time=0;
	for(int i=1;i<=n;i++)//求出该计划出行的时间区间 
	{
		scanf("%d%d", &time, &last);
		if(max_time<time)max_time=time;
		if(time-last+1>1)arr[time-last+1]++;
		else arr[1]++;
		arr[time+1]--;
	}
	for(int i=2;i<=max_time+1;i++)
	{
		arr[i]=arr[i]+arr[i-1];//求出每个时间点的计划个数 
	}
	int q=0;
	for(int i=1;i<=m;i++)
	{
		scanf("%d", &q);
		if((q+k)>max_time)count[i]=0;//当核酸检测结果出来时间大于计划的最大时间,则该时间点没计划 
		else count[i]=arr[q+k];
	} 
	for(int i=1;i<=m;i++)
	{
		printf("%d\n", count[i]);
	}
	return 0;
}

这里的巧妙之处是,把每个时间点可出行的计划数存起来,在输入计划的时候,用1和-1标记该计划可出行的时间段(1表示该时间点有一个计划,并且,注意看标记-1的时间点,是该计划出行的下一个时间点,为了抵消掉前面前缀和的影响),在后面求前缀和的时候再全部相加,先标记差分区间,再求前缀和,故曰差分前缀和算法。

希望能帮助到大家!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值