0x03.基本算法 — 前缀和与差分
目录
- 一、前缀和
- 二、二维前缀和
- 1.二维前缀和的修改和求和
- 0. NOI 2003激光炸弹(二维前缀和)
- 1.牛妹吃豆子(二维前缀和模板,修改+求和)
- 2.静态数组的区间求和问题
- 3.静态维护区间加等差数列的求和问题
- 三、差分
- 3.IncDec Sequence(增减序列 )(差分+贪心)
- 4. [USACO07JAN]Tallest Cow (差分)
- 5.P3406 海底高铁(差分,前缀和,贪心)
声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了 按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
下方链接为学习笔记目录链接(中转站)
学习笔记目录链接
ACM-ICPC在线模板
一、前缀和
给定一个序列,定义pre[i]=pre[i−1]+a[i]pre[i]=pre[i-1]+a[i]pre[i]=pre[i−1]+a[i]
可求区间[l,r]的和:pre[r]−pre[l−1]pre[r]-pre[l-1]pre[r]−pre[l−1]
可求区间的[l,r]的异或和:(异或的逆运算是它本身)所以即为pre[r]^ pre[l-1]
二、二维前缀和
二维的前缀和根据容斥原理推导得到的
定义:pre[i][j]=pre[i−1][j]+pre[i][j−1]−pre[i−1][j−1]+a[i][j],pre[i][j]pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+a[i][j], pre[i][j]pre[i][j]=pre[i−1][j]+pre[i][j−1]−pre[i−1][j−1]+a[i][j],pre[i][j]表示的是(i,j)(i,j)(i,j)右下角的一个子矩形
1.二维前缀和的修改和求和
如果要使区间x1<=x<=x2x_1<=x<=x_2x1<=x<=x2,y1<=y<=y2y_1<=y<=y_2y1<=y<=y2里的数全部+1那么需要进行下面的操作(利用差分思想):
void add(ll x_1,ll y_1,ll x_2,ll y_2)
{a[x_1][y_1]++;a[x_2+1][y_2+1]++;a[x_1][y_2+1]--;a[x_2+1][y_1]--;
}
注意运用前缀和修改矩阵以后要先求一遍前缀和还原原矩阵,然后再求一遍前缀和才是真正的前缀和,这样才能继续进行下一步的操作
具体实例见下见下面的例题模板:牛妹吃豆子
0. NOI 2003激光炸弹(二维前缀和)
输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
这里用到了二维前缀和。
本题种有几点需要注意:
1.我们把(xi,yi)(xi,yi)(xi,yi)(xi,yi)(xi,yi)(xi,yi)作为一个格子,但是实际上他们只是一个点,所以说我们不妨认为这个点就是这个格子的中心,既然如此的话我们就可以认为是(xi−0.5,yi−0.5)(xi−0.5,yi−0.5)(xi−0.5,yi−0.5)
2. 本题中的空间给的很小,只有125MB,所以计算前缀和的时候就不用开两个数组了,直接用一个数组来算就好,反正前缀和只与前面的状态有关,当前的状态无关。
3. 因为题目要求边上的目标是不能炸到的,所以这里实际上是求r-1的前缀和,回到代码里就是i−ri-ri−r,不然应该写i−r−1i-r-1i−r−1
4. 然后剩下的就是基本的前缀和操作了,取范围为r 的前缀和即可
#include<iostream>
#include<string.h>
#include<cstdio>
#include<math.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef int ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=5002;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0ll n,m,r;
ll s[N][N];
int main()
{scanf("%d%d",&n,&r);while(n--){ll x,y,val;scanf("%d%d%d",&x,&y,&val);s[x+1][y+1]=val;}over(i,1,5001)over(j,1,5001){s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+s[i][j];}ll ans=0;over(i,0,5001-r)over(j,0,5001-r){ll sum=s[i+r][j+r]-s[i+r][j]-s[i][j+r]+s[i][j];ans=max(ans,sum);}printf("%d\n",ans);return 0;
}
1.牛妹吃豆子(二维前缀和模板,修改+求和)
题目链接:https://ac.nowcoder.com/acm/contest/5158/D
注意运用前缀和修改矩阵以后要先求一遍前缀和还原原矩阵,然后再求一遍前缀和才是真正的前缀和
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>
#include<vector>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=2005;
const ll INF=1e10+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;ll n,m,a[N][N];void add(ll x_1,ll y_1,ll x_2,ll y_2)
{a[x_1][y_1]++;a[x_2+1][y_2+1]++;a[x_1][y_2+1]--;a[x_2+1][y_1]--;
}
ll k,q;
ll x_1,x_2,y_1,y_2;
int main()
{scanf("%lld%lld%lld%lld",&n,&m,&k,&q);while(k--){scanf("%lld%lld%lld%lld",&x_1,&y_1,&x_2,&y_2);add(x_1,y_1,x_2,y_2);}over(i,1,n)over(j,1,m)//求两次前缀和a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+a[i][j];over(i,1,n)over(j,1,m)a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+a[i][j];while(q--){scanf("%lld%lld%lld%lld",&x_1,&y_1,&x_2,&y_2);//求区间的和ll ans=a[x_2][y_2]+a[x_1-1][y_1-1]-a[x_1-1][y_2]-a[x_2][y_1-1];printf("%lld\n",ans);}return 0;
}
2.静态数组的区间求和问题
静态数组的区间求和问题
3.静态维护区间加等差数列的求和问题
静态维护区间加等差数列的求和问题
三、差分
对于一个给定的数列A,它的差分数列B定义为:
B[1]=A[1],B[i]=A[i]−A[i−1](2<=i<=n)B[1]=A[1],B[i]=A[i]-A[i-1](2<=i<=n)B[1]=A[1],B[i]=A[i]−A[i−1](2<=i<=n)
前缀和与差分运算为互逆运算,任意一个数组A 的前缀和数组的差分数组是它本身。
让区间[l,r][l,r][l,r]里的数均+k就是让原数列的差分数组a,a[l]+=k;a[r+1]−=ka[l]+=k;a[r+1]-=ka[l]+=k;a[r+1]−=k然后for(inti=1;i<=n;i++)a[i]+=a[i−1]for(int i=1;i<=n;i++) a[i]+=a[i-1]for(inti=1;i<=n;i++)a[i]+=a[i−1]即可还原数组,所以千万注意修改区间之后要求一遍前缀和才能差分数组转换成修改过后的原数组,再进行各种操作。(应该不会有人像我这么菜忘了求原数组直接乱搞一通WA⑧,真的丢人,我得记一辈子 )
具体的操作如下:
#include<iostream>
#include<string.h>
#include<cstdio>
#include<math.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,全部换成int看会不会A,别动这里!!!
const ll N=110000;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0ll b[N];
ll n,m,a[N],pos,neg;
int main()
{scanf("%lld%lld",&n,&m);over(i,1,n)scanf("%lld",&a[i]),b[i]=a[i]-a[i-1];over(i,1,m){ll l,r,k;scanf("%lld%lld%lld",&l,&r,&k);b[l]+=k,b[r+1]-=k;}over(i,1,n)a[i]=a[i-1]+b[i],printf("%lld ",a[i]);return 0;
}
输入:
5 3
1 1 1 1 1
1 5 1
1 4 1
2 3 1
输出:
3 4 4 3 2
差分思想在很多地方应用很广,比如要求使一个数列中的数全部相同,就可以转化成使这个数列的差分数组全部为0。比如下面这道例题:
3.IncDec Sequence(增减序列 )(差分+贪心)
链接:https://www.acwing.com/problem/content/description/102/
输出:
1
2
思路:
利用上面的性质,因为我们只要求A序列中所有的数相同,而不在意这些方案具体是什么,所以说我们就可以转化题目,也就是将对A序列的+1,−1+1,−1+1,−1操作,让A序列相同,改成目标把B2,…,BnB_2,…,B_nB2,…,Bn变成全0即可,也就是A序列全部相等。
利用差分的修改:a[l]+=1;a[r+1]−=1a[l]+=1;a[r+1]-=1a[l]+=1;a[r+1]−=1,每一次选取一个Bi和Bj,(2<=i,j<=n),而且这两个数,一个为正数,一个为负数,至于为什么要是正负配对,因为我们是要这个B序列2~n都要为0,所以这样负数增加,正数减少,就可以最快地到达目标为0的状态。
至于那些无法配对的数Bk可以选B1或者Bn+1,这两个不影响的数,进行修改。
所以说我们这道题目得出的答案就是,最少操作数min(pos,neg)+abs(pos−neg)=max(pos,neg)min(pos,neg)+abs(pos−neg)=max(pos,neg)min(pos,neg)+abs(pos−neg)=max(pos,neg),然后最终序列a可能会有abs(pos−neg)+1abs(pos−neg)+1abs(pos−neg)+1种情况。
positive 正数 negative 负数
#include<iostream>
#include<string.h>
#include<cstdio>
#include<math.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,全部换成int看会不会A,别动这里!!!
const ll N=110000;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0ll n,m,a[N],p,q;
int main()
{scanf("%lld",&n);over(i,1,n)scanf("%lld",&a[i]);over(i,2,n){ll c=a[i]-a[i-1];if(c>0)p+=c;else q-=c;}printf("%lld\n%lld\n",max(p,q),abs(p-q)+1);return 0;
}
4. [USACO07JAN]Tallest Cow (差分)
https://www.luogu.com.cn/problem/P2879
输入:
9 3 5 5
1 3
5 3
4 3
3 7
9 8
输出:
5
4
5
3
4
4
5
5
5
题目中给我们m对关系,实际上是告诉我们a和b之间的所有的牛都比a,b 矮,而题目中要求的是所有的牛高度的最大值,所以利用差分的思想,标记从a+1到b-1之间的都-1,也就是 A[a+1]--,A[b]++;
,因为第p头牛一定是最高的,所以A[p]=0
但是因为输入的数据一定合法所以这个可以忽略不计。这样每头牛的高度都是A[i]+h
,利用差分的思想O(m+n)O(m+n)O(m+n)的时间复杂度下修改即可。
还有一点非常坑爹的就是数据中的a,b可能会重复多次输入不愧是USAOC的毒瘤题,输入输出是真的狠,所以要判重,这里直接开一个map,因为如果直接开bool数组可能会爆内存。还有就是a可能大于b,所以要先判断。每次写题前先把数据处理好是一个好习惯,虽然一般的比赛的数据都不会这么毒瘤,但是谁又说得准呢,小心点没毛病
#include<iostream>
#include<string.h>
#include<cstdio>
#include<math.h>
#include<map>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,全部换成int看会不会A,别动这里!!!
const ll N=110000;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0int n,m,h,p;
map<pair<int,int>,bool>existed;
ll A[N],B[N];
int main()
{cin>>n>>p>>h>>m;over(i,1,m){ll a,b;scanf("%lld%lld",&a,&b);if(a>b)swap(a,b);if(existed[make_pair(a,b)])continue;A[a+1]--,A[b]++;existed[make_pair(a,b)]=true;}over(i,1,n){B[i]=B[i-1]+A[i];printf("%lld\n",B[i]+h);}return 0;
}
5.P3406 海底高铁(差分,前缀和,贪心)
P3406 海底高铁
题目背景
大东亚海底隧道连接着厦门、新北、博艾、那霸、鹿儿岛等城市,横穿东海,耗资1000亿博艾元,历时15年,于公元2058年建成。凭借该隧道,从厦门可以乘坐火车直达台湾、博艾和日本,全程只需要4个小时。
题目描述
该铁路经过N个城市,每个城市都有一个站。不过,由于各个城市之间不能协调好,于是乘车每经过两个相邻的城市之间(方向不限),必须单独购买这一小段的车票。第i段铁路连接了城市i和城市i+1(1<=i<N)。如果搭乘的比较远,需要购买多张车票。第i段铁路购买纸质单程票需要Ai博艾元。
虽然一些事情没有协调好,各段铁路公司也为了方便乘客,推出了IC卡。对于第i段铁路,需要花Ci博艾元的工本费购买一张IC卡,然后乘坐这段铁路一次就只要Bi(Bi<Ai)元。IC卡可以提前购买,有钱就可以从网上买得到,而不需要亲自去对应的城市购买。工本费不能退,也不能购买车票。每张卡都可以充值任意数额。对于第i段铁路的IC卡,无法乘坐别的铁路的车。
Uim现在需要出差,要去M个城市,从城市P1出发分别按照P1,P2,P3…PM的顺序访问各个城市,可能会多次访问一个城市,且相邻访问的城市位置不一定相邻,而且不会是同一个城市。
现在他希望知道,出差结束后,至少会花掉多少的钱,包括购买纸质车票、买卡和充值的总费用。
输入格式
第一行两个整数,N,M。接下来一行,M个数字,表示Pi接下来N-1行,表示第i段铁路的Ai,Bi,Ci。
输出格式
一个整数,表示最少花费。
KKK的思路:对于其中一小段,我们要么全部买纸票,要么全部刷卡。
所以我们只需要统计每一小段经过的总次数。
如果你暴力模拟统计的话,估计会tle。
怎么快速知道每一段次数呢?
我们回忆一下“借教室”这道题。
如果你高兴的话,可以上线段树或者树装数组——但是没必要。
假设有5段,我们把1到3加上1,可以像图2一样,开头+1,结尾-1。然后2到4加1,如图3.
+1 -1 +1 +1 -1 -1- - - - - -- - - -- - -- -- - -- --1 2 3 4 5 1 2 3 4 5 1 2 3 4 5图1 图2 图3
然后呢,我们求出每一项前缀和(就是从前往后加):
1 2 2 1 0
- - - - -
1 2 3 4 5图4
然后每一段直接贪心比较,然后就没有然后了
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
ll n,m,ans,a[maxn],b[maxn],c[maxn],x,y,t[maxn],d[maxn];
int main()
{ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>n>>m;for(int i=1;i<=m;i++)cin>>d[i];for(int i=1;i<=n-1;i++)cin>>a[i]>>b[i]>>c[i];for(int i=1;i<=m-1;i++){x=min(d[i+1],d[i]);//左端点和右端点y=max(d[i+1],d[i]);t[x]++;t[y]--;//从1到4是经过了123,所以y已经是右端点+1了}for(int i=1;i<=n;i++)//前缀和t[i]+=t[i-1];for(int i=1;i<=n-1;i++)ans+=min(a[i]*t[i],b[i]*t[i]+c[i]);//贪心求最小花费cout<<ans<<endl;return 0;
}
注:如果您通过本文,有(qi)用(guai)的知识增加了,请您点个赞再离开,如果不嫌弃的话,点个关注再走吧,日更博主每天在线答疑 ! 当然,也非常欢迎您能在讨论区指出此文的不足处,作者会及时对文章加以修正 !如果有任何问题,欢迎评论,非常乐意为您解答!( •̀ ω •́ )✧
0x03.基本算法 — 前缀和与差分相关推荐
- 基本算法——前缀和与差分
一.前缀和 一维前缀和 顾名思义,不再赘述. #include <iostream> #include <cstdio> #include <algorithm> ...
- c++算法基础必刷题目——前缀和与差分
文章目录 前缀和与差分算法: 1.校门外的树 2.值周 3.中位数图 4.激光炸弹 5.二分 6.货仓选址 前缀和与差分算法: 前缀和与差分算法主要是为了快速求出某个区间的和,例如有一个数组a[1 ...
- Acwing算法基础【1】基础(三)前缀和与差分
目录 三.前缀和与差分 3.1 前缀和 3.1.1 一维前缀和的算法思想 3.1.2 一维前缀和的代码实现 3.1.3 二维前缀和的基本思想 3.1.4 二维前缀和的代码实现 3.2 差分 3.2.1 ...
- 基本算法之前缀和与差分的是使用
前缀和与差分 前缀和 鸣谢 二维前缀和 激光炸弹 差分 求差分 差分求区间修改 增减序列 最高的牛 前缀和 鸣谢 添加链接描述 和 添加链接描述 二维前缀和 激光炸弹 题目链接 解题思路: 解法思路: ...
- 前缀和,差分算法训练
知识点: 一维前缀和 二维前缀和 一维差分 二维差分 题目: 蓝桥杯2017初赛-k倍区间-前缀和 [蓝桥杯2018初赛]递增三元组-双指针,枚举,排序,前缀和
- 算法基础(2) | 高精度、前缀和、差分
文章目录 一.高精度 1.1 高精度加法 1.2 高精度减法 1.3 高精度乘法 1.4 高精度除法 二.前缀和 2.1 一维前缀和 2.2 二前缀和 三.差分 3.1 一维差分 3.2 二维差分 四 ...
- 基础算法(二):高精度/前缀和与差分
目录 一.高精度 加法高精度 减法高精度 乘法高精度(大数乘以小数) 除法高精度(高精度整数除以低精度整数) 二.前缀和与差分 一维前缀和 二维前缀和 差分 二维差分 一.高精度 数组下标低位存储数字 ...
- 信奥中的数学:前缀和与差分、大整数开方技巧
[算法2-1]前缀和与差分 [算法2-1]前缀和与差分 - 题单 - 洛谷 前缀和与差分 图文并茂 超详细整理(全网最通俗易懂) 前缀和与差分 图文并茂 超详细整理(全网最通俗易懂)_林深不见鹿 的博 ...
- 前缀和与差分 图文并茂 超详细整理(全网最通俗易懂)
目录 1.前缀和 2.前缀和算法有什么好处? 3.二维前缀和 4.差分 5.一维差分 6.二维差分 1.前缀和 前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的 ...
最新文章
- 暑期集训2:ACM基础算法 例1:POJ-1064
- android 应用程序框架
- Leaflet中绘制同心圆、多个中心对称多边形、平行四边形网格、矩形网格
- 乌班图系统16.04安装
- 《零基础看得懂的C++入门教程 》——(9)结构体原来如此
- P3960-列队【权值线段树】
- c_str()的用法
- 安卓应用用户数据_用户指标数据应用
- repo 获取各个库的tag代码或者分支代码
- Android 性能优化----(3)内存优化指南
- 唐诗三百首加密软件如何使用_绿盾加密软件如何设置邮件白名单
- 计算机网络和共享中心打不开,Win7系统网络和共享中心打不开如何解决
- 浅谈如何在优麒麟22.04中使用Eigenstrat和Plink工具生成类23andMe格式原始数据
- Linux查看流量情况以及关闭流量端口
- 最新时下最火的盲盒商城源码/视频搭建教程
- 【阿里云】云服务器ECS怎么一键重装系统
- 微服务(二)——注册中心(Eureka、Nacos)
- 什么是粗粒度和细粒度权限
- 前台技术--window.showModalDialog带来的浏览器兼容问题
- 全套Python零基础学习资料,电子书整理好了,想要进行技术提升,转行的自取!