前缀和 & 差分
前缀和 & 差分
-
一般作为优化策略。
-
前缀和是解决一些区间多次修改但查询次数较少的题目,定义新数组 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]\),则满足:
差分可以实现快速区间修改,例如要将 数组 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;
二维前缀和 & 差分
主要是公式
二维前缀和预处理(计算)公式
二维前缀和求解公式:
推导过程画图,记住公式就能做题
应用
题目描述
你需要选择一块土地,土地被认为是一个\(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 (提交记录有解析)[仅限本人看,题单为团队私有]
本文作者:SXqwq,转载请注明原文链接:https://www.cnblogs.com/SXqwq/p/17389419.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!