前缀和 & 差分

前缀和 & 差分

  • 一般作为优化策略。

  • 前缀和是解决一些区间多次修改但查询次数较少的题目,定义新数组 sum[N], 原数组a[N],则令

    \[sum[i] = sum[i-1] + a[i] \]

    显然时间复杂度为O(N),需要注意,下标至少从1开始,否则取 i-1 时会越界

    例题 :

    小 K 打下的江山一共有 \(n\) 个城市,城市 \(i\) 和城市 \(i+1\) 有一条双向高速公路连接,走这条路要耗费时间 \(a_i\)

    小 K 为了关心人民生活,决定定期进行走访。他每一次会从 \(1\) 号城市到 \(n\) 号城市并在经过的城市进行访问。其中终点必须为城市 \(n\)

    不仅如此,他还有一个传送器,传送半径为 \(k\),也就是可以传送到 \(i-k\)\(i+k\)。如果目标城市编号小于 \(1\) 则为 \(1\),大于 \(n\) 则为 \(n\)

    但是他的传送器电量不足,只能传送一次,况且由于一些原因,他想尽量快的完成访问,于是就想问交通部部长您最快的时间是多少。

    注意:他可以不访问所有的城市,使用传送器不耗费时间

    分析

    • 显然需要尽可能往前跳,故不考虑向后跳的方案
    • 显然时间一定为正,故跳 k 个一定是最优的,不存在跳不到k个就最优的情况
    • 暴力思想是从下标 k 作为跳跃终点枚举长度(因为不存在跳不到 k 个就最优的情况,故倒着搜),将跳跃最大值从 \(ans\) 中删去,会T
    • 使用前缀和优化计算跳跃距离过程

    代码

    int main()
    {
    	scanf("%lld%lld",&n,&k);
    	for(ll i=1;i<=n;i++)
    	{
    		scanf("%lld",&a[i]);
    		c[i] = c[i-1] + a[i];
    	}
    	if(k >= n)
    	{
    		cout<<"0"<<endl;
    		return 0;
    	}
    	for(ll i=k;i<=n;i++) //枚举跳越目的地,倒着搜 
    	{
    		maxn = max(maxn,c[i]-c[i-k]);
    	}
    	cout<<c[n-1]-maxn<<endl; //不跳越的路程-跳越的路程 
    } 
    

    显而易见,前缀和一般不作为单独算法考察,是优化暴力的一种方法。

    此类题一般先想常规做法, 再看看复杂度高的地方能否用前缀和优化。

差分

  • 和前缀和一样作为优化策略
  • 是前缀和的逆运算

一般地,定义差分数组 \(c[N]\),原数组为\(a[N]\),则满足:

\[c[i] = a[i] - a[i-1] \]

差分可以实现快速区间修改,例如要将 数组 a l 到 r 每一位 + k,则只需要:

a[l] += k;
a[r+1] -= k ;

实现 O(1)的区间修改,最后将差分数组求前缀和就是修改后的数组

为了操作方便,我们通常将差分的操作过程封装为一个函数:

void add(int l,int r,int x) 
{
    c[l] += x;
    c[r+1] -= x;
}

求差分的时候也可以调用函数,只需要:

for(int i=1;i<=n;i++)
{
    scanf("%d",&a[i]);
    add(i,i,a[i]);
}

应用

  • 差分可以处理大量区间修改,如 SD CSPJSX2023 T1 ,属于板子,太明显
  • 差分还可以记录每一段,具体如下:
例题

该铁路经过 \(N\) 个城市,每个城市都有一个站。不过,由于各个城市之间不能协调好,于是乘车每经过两个相邻的城市之间(方向不限),必须单独购买这一小段的车票。第 \(i\) 段铁路连接了城市 \(i\) 和城市 \(i+1(1\leq i<N)\)。如果搭乘的比较远,需要购买多张车票。第 \(i\) 段铁路购买纸质单程票需要 \(A_i\) 博艾元。

虽然一些事情没有协调好,各段铁路公司也为了方便乘客,推出了 IC 卡。对于第 \(i\) 段铁路,需要花 \(C_i\) 博艾元的工本费购买一张 IC 卡,然后乘坐这段铁路一次就只要扣 \(B_i(B_i<A_i)\) 元。IC 卡可以提前购买,有钱就可以从网上买得到,而不需要亲自去对应的城市购买。工本费不能退,也不能购买车票。每张卡都可以充值任意数额。对于第 \(i\) 段铁路的 IC 卡,无法乘坐别的铁路的车。

Uim 现在需要出差,要去 \(M\) 个城市,从城市 \(P_1\) 出发分别按照 \(P_1,P_2,P_3,\cdots,P_M\) 的顺序访问各个城市,可能会多次访问一个城市,且相邻访问的城市位置不一定相邻,而且不会是同一个城市。

现在他希望知道,出差结束后,至少会花掉多少的钱,包括购买纸质车票、买卡和充值的总费用。

解析

先考虑暴力做法,因为可能会重复经过一些段,所以我们需要暴力将每一段经过的次数记录下来,然后ans + 每一段买卡还是买票更省钱的一种方式

但是复杂度会很高,我们又发现记录每一段经过的次数过程可以使用差分,只需要首+1,尾-1(因为尾代表r -> r+1的票,不属于我们所求的,所以是尾-1而不是尾的下一位-1)

代码
int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%lld",&p[i]);
	}
	for(int i=1;i<m;i++)
	{
		cha[min(p[i],p[i+1])] ++;
		cha[max(p[i],p[i+1])] --; //差分处理,需要格外注意,见上 
	}
	for(int i=1;i<n;i++)
	{
		cha[i] = cha[i-1] + cha[i]; //将差分数组求前缀和得到每段铁路走过的次数 
	} 
	for(int i=1;i<n;i++)
	{
		scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
		ans += min(cha[i]*a[i],cha[i]*b[i]+c[i]); //依题意模拟 
	}
	cout<<ans<<endl;

二维前缀和 & 差分

主要是公式

二维前缀和预处理(计算)公式

\[f[i][j] = f[i-1][j]+f[i][j-1]+f[i-1][j-1]+a[i][j] \]

二维前缀和求解公式:

\[ans =f[x2][y2]-f[x1-1][y2]-f[x2][y1-1]+f[x1][y1] \]

推导过程画图,记住公式就能做题

应用

题目描述

你需要选择一块土地,土地被认为是一个\(C*C\)的正方形,使得土地的价值最高(土地上每个坐标的价值和最大)

input

第一行共3个整数,\(N,M,C\)表示总地图的宽和长以及土地边长

接下来\(N*M\)个整数为每个坐标的价值

output

共1行,表示所求土地左上角的坐标

分析

二维前缀和板子,求解过程非常容易,枚举左上角的坐标需要注意边界,只能枚举到\(n-c+1\),不然越界

代码
void work()  
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			f[i][j] = f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];
		}
	}
}
int main()
{
	scanf("%lld%lld%lld",&n,&m,&c);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++) scanf("%lld",&a[i][j]);
	}
	work();
	for(int i=1;i<=n-c+1;i++)
	{
		for(int j=1;j<=m-c+1;j++)
		{
			ll x1=i,y1=j,x2=i+c-1,y2=j+c-1; //画图得知右下角坐标 
			ll p = f[x2][y2]- f[x1-1][y2] -f[x2][y1-1]+f[x1-1][y1-1];//二维前缀和计算 
			if(p > maxn)
			{
				ax = i;
				ay = j;
				maxn = p;
			}
		}
	}
	cout<<ax<<" "<<ay<<endl;
	return 0;
}

复习题单:XDOI https://www.luogu.com.cn/training/309854 (提交记录有解析)[仅限本人看,题单为团队私有]

posted @   SXqwq  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示