前缀和
顾名思义:用某一个数组来记录数组a前i项和,这个还可以用来求区间[l,r]的和:s[r]-s[l-1](因为第l项也在区间内)
话不多说,直接上例题:LeetCode 5393
- 首先,暴力搜索,TLE。
- 很明显,左边拿的+右边拿的=k张,且拿了前面的才能那拿面的,有很明显的前缀和后缀性质,枚举左边拿的张数i,对应的右边只能拿k-i张,线性时间。
- 且枚举后缀的时候有个技巧,并不需要真正的后缀,具体看代码。
AC代码:
const int MAXN = 1e5 + 5;
class Solution {
public:
int pre[MAXN],suf[MAXN];
int maxScore(vector<int>& cardPoints, int k) {
int n = cardPoints.size();
memset(pre,0,sizeof(pre));
memset(suf,0,sizeof(suf));
for(int i = 1;i <= n;++i){
pre[i] += (pre[i - 1] + cardPoints[i - 1]);
suf[i] += (suf[i - 1] + cardPoints[n - i]); //!!!
}
int ans = 0;
for(int i = 0;i <= k;++i){ //枚举左边拿的张数
ans = max(ans,pre[i] + suf[k - i]);
}
return ans;
}
};
前缀和与差分
传送门:洛谷P3397
- 此题,假如暴力,枚举每个点,再枚举每个区间,试想下最坏情况,每个区间都是整张图,那就是O(nn m n n),明显TLE。
- 试着做下预处理,优化一下:先枚举所有区间,给区间内的点加上1表示被一个毯子覆盖了,然后再遍历下所有点。第一步时间复杂度最坏为O(mn2),第二步为O(n2),总的为O(mn2),照理说n=m=103,应该过不了,可能数据水了点。
下面贴下AC代码(后面有利用差分的优化):
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 1000 + 5;
int n,m;
int ans[maxn][maxn];
int main(void){
scanf("%d%d",&n,&m);
memset(ans,0,sizeof(ans));
int x1,y1,x2,y2;
for(int i = 0;i < m;++i){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
for(int x = x1;x <= x2;++x){
for(int y = y1;y <= y2;++y) ans[x][y]++;
}
}
for(int x = 1;x <= n;++x){
for(int y = 1;y <= n;++y){
if(y == 1) printf("%d",ans[x][y]);
else printf(" %d",ans[x][y]);
}
printf("\n");
}
return 0;
}
- 对上一步进行优化,上一步慢就慢在每个毯子内的所有点都要枚举,然后加1作为此点被一个毯子覆盖的标记,所以我们需要二维差分对这一步进行优化。
- 不懂二维差分的请移步:https://www.cnblogs.com/Roni-i/p/9354177.html
- 这样子,我们通过差分就少枚举了很多点,时间复杂度最差降为O(m*n+n2)。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 1000 + 5;
int n,m;
int ans[maxn][maxn],p[maxn][maxn];
int main(void){
scanf("%d%d",&n,&m);
memset(ans,0,sizeof(ans));
memset(p,0,sizeof(p));
int x1,y1,x2,y2;
for(int i = 0;i < m;++i){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
for(int row = y1;row <= y2;row++){
p[x1][row]++; p[x2 + 1][row]--; //注意这边是x2+1
}
}
for(int x = 1;x <= n;++x){
for(int y = 1;y <= n;++y){
ans[x][y] = ans[x-1][y] + p[x][y];
if(y == 1) printf("%d",ans[x][y]);
else printf(" %d",ans[x][y]);
}
printf("\n");
}
return 0;
}
再来一道前缀和/差分:洛谷P3406
- 读完题大概就知道要统计出每段铁路经过次数,然后贪心求解:要么全选票,要么全时买卡后充值。因为n和m为105,很明显要用差分的思想,然后求前缀和(因为用了差分,所以求每段铁路的访问次数时要求前缀和)。
- 为什么可以贪心:假设最优解中两种方式都选择了,但是题目说bi<ai,既然我都买了卡,干嘛剩下的不都通过充值的方式买呢,不然亏大了。
- 还有要注意,因为打算求前缀和,所以当后面访问的城市在前面访问的城市的前面的时候(返回乘车),仍然需要给地图上后面城市的那段铁路做上差分标记,所以需要分类讨论下位置。
- 这题所有都要开longlong,除了m和n,因为费用可能很高。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn = 1e5 + 5;
LL a[maxn],b[maxn],c[maxn],times[maxn],city[maxn];
int n,m;
int main(void){
memset(times,0,sizeof(times));
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;++i) cin >> city[i];
for(int i = 1;i <= n - 1;++i) cin >> a[i] >> b[i] >> c[i];
//次数
for(int i = 2;i <= m;++i){
if(city[i] < city[i-1]){
times[city[i-1]]--; times[city[i]]++;
}
else if(city[i] > city[i-1]){
times[city[i]]--; times[city[i-1]]++;
}
}
LL ans = 0;
for(int i = 1;i < n;++i){
times[i] += times[i-1]; ans += min(times[i]*a[i],c[i] + b[i] * times[i]);
}
cout << ans;
return 0;
}