目录
C.海底高铁(除了我没做的那道题,我认为这道题是最难的TAT)
前缀和的公式:
一维:s[ i ] = a[ i ] + s[ i-1 ]
二维:s[ i ] [ j ] = a[ i ] [ j] + s[ i-1 ] [ j ] + s[ i ] [j-1 ] - s[ i-1 ] [ j-1 ]
差分的公式:
一维:b[ i ] =s[ i ] - s[ i-1 ]
二维:b[ i ] = s[ i ] [ j ] - s[ i-1 ] [ j ]-s[ i ] [ j-1 ]+s[ i-1 ] [ j-1 ]
洛谷题单:
这里提醒做题一定要注意数据范围!!!
A.最大子段和
分析:
先求每个位置的前缀和(某个区间求和前缀和可以说是最快的),然后去找该位置前前缀和的最小值,如果要求一段和最大,就要用这段和减去前面最小的值(最开始我的想法是直接先求出所有的前缀和找最小值,然后挨个从1开始循环,去找从不同位置不同个数的前缀和,但是很不幸运,循环太多,超时了QAQ)
代码:
代码中的亮点已注明
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int n,a[N],s[N],ans[N];
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];//前缀和
}
//如果要求一段和最大,就要用这段和减去前面最小的值
int minn=min(0,s[1]);//这里与0比较就是看s[i]-1与是s[i]-min(min!=0时)谁大!!!!!
ans[1]=s[1];//从s[1]开始
for(int i=2;i<=n;i++){
ans[i]=s[i]-minn;
minn=min(minn,s[i]);
}
int res=-9999;
for(int i=1;i<=n;i++)res=max(res,ans[i]);
printf("%d\n",res);
//这里不可以用下面的方式找出ans数组中的最小值,因为数组开得很大存在0,但是可能测试样例得最大数值小于0
//sort(ans,ans+n,cmp);
//printf("%d\n",ans[0]);
return 0;
}
B.地毯
分析:
看到这里的时候,我就想到了一个矩阵的某个子矩阵进行加减,瞬间想到二维差分和二位前缀和,二位差分的公式为:
由差分算的二位前缀和公式:
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int a[N][N]={0},s[N][N]={0},n,m;
int main()
{
scanf("%d%d",&n,&m);
while(m--){
int x11,y11,x2,y2;
scanf("%d%d%d%d",&x11,&y11,&x2,&y2);
a[x11][y11]+=1;//进行子矩阵的加减,差分
a[x2+1][y11]-=1;
a[x11][y2+1]-=1;
a[x2+1][y2+1]+=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
s[i][j] = a[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];//把之前的加减结果进行求和
printf("%d ",s[i][j]);//注意输出格式,每个数带一个空格
}
printf("\n");//结束一行的输出输出一个换行符号
}
return 0;
}
C.海底高铁(除了我没做的那道题,我认为这道题是最难的TAT)
分析:
这道题我自己觉得好绕,读题都理解了好久,最后还是看别人的题解慢慢理解了题意,然后最后解决了这道题,简直太佩服大佬了orz。
这道题读懂了题意可以看作是①每一段的最小费用加起来则总体费用最小→②这道题还有个难点就是这里的区间是线段而不是一个具体的数,所以我们需要以一个统一的标准进行区间段的区分:于是我们想到了以每个区间的左端点值进行整个线段的记录→③然后节约时间可以对某段区间做同样的加减数的方法:想到的就是差分(当然有差分就会有由差分求前缀和)→④最后用得到的线段数比较两种购买方案→⑤最后得出答案
注意:
当然代码中还有很多需要记录的细节!!!例如:线段数是站点数-1,同时差分和前缀和的循环最好是从1开始(涉及边界问题)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;//注意数据范围,不开longlong见祖宗,可能用不到,但是最好开!!!
int main(){
ll n,m,ans=0;
scanf("%lld%lld",&n,&m);
ll p[m+1]; //用于记录经过站点的顺序
ll t[n+1]={}; //用于记录站点之间的路径经过的次数
ll a[n+1],b[n+1],c[n+1],x,y;//用于记录每段路径所花的费用
for(ll i=1;i<=m;i++)
scanf("%lld",&p[i]);
for(ll i=1;i<=n-1;i++)
scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
for(ll i=1;i<=m-1;i++){//所有的区间都以较小的点排在前面,例如:2-1,5-3都用1-2,3-5表示,且每一段都用前面较小的点作为标记!!!!
if(p[i]>p[i+1]){
x=p[i+1];
y=p[i];
}
else{
x=p[i];
y=p[i+1];
}
t[x]++;
t[y]--;
}
for(ll i=1;i<=n;i++){//求前缀和
t[i]+=t[i-1];
}
for(ll i=1;i<=n-1;i++)
ans+=min(a[i]*t[i],(b[i]*t[i]+c[i]));//求总的最小就是把每一段的最小相加
printf("%lld",ans);
return 0;
}
D.最大加权矩阵
分析:
这道题题意很明确:就是去找一个大矩阵中的子矩阵和中的最大值
①首先求一个二维前缀和公式:s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]→②再求子矩阵前缀和,(一个矩阵的确定要确定两个端点:左上角x1y1和右下角x2y2)记得一个公式:用a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]
注意:
就是找子矩阵的端点问题,四重循环来找,同时注意端点的边界问题,右下角的坐标一定大于左上标。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=130;
int n,a[N][N],s[N][N],mx=-10000;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
s[i][j]=a[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
}
}
for(int x1=1;x1<=n;x1++){
for(int y1=1;y1<=n;y1++){
for(int x2=1;x2<=n;x2++){
for(int y2=1;y2<=n;y2++){
if(x2<x1 || y2<y1) continue;//如果左上角比右下角还要大,就不用求了,下一个
mx=max(mx,s[x2][y2]+s[x1-1][y1-1]-s[x2][y1-1]-s[x1-1][y2]);//求最大值
}
}
}
}
printf("%d\n",mx);
return 0;
}
E.领地选择
分析:
①首先进行求二位数组的前缀和→②用两重循环先进行找左上角的端点再用规定的规格找右下角的坐标→③求出子矩阵的和寻找最大值,记录最大矩阵的两个端点
注意:
①A-B=C,说明A比B大C个单位,但是如果这里用来算隔了几个各子就要算上本身则为A-B-1(找端点的时候用)
②这里给定了子矩阵的规格大小,所以在找端点的时候注意边界问题
③为什么在写题解前就在说注意数据范围的问题喃,就是因为其实这道题题意也很容易理解,但是就是因为最后两组数据没有过所以一直都是85分,最后才发现是数据开得不够大TAT(这里的数据者的是ans的起始值!!!)。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,m,c;
int a[N][N]={0},s[N][N]={0};
int main()
{
scanf("%d%d%d",&n,&m,&c);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
s[i][j]=a[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
}
}
int x2,y2,ans,maxx=-2147483648,xx,yy;//负数一定要给得大,最开始最后一组数据没过就是因为负数不够大!!!
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++){
if((x+c-1>n)||(y+c-1>m))continue;//找右上角,矩阵的确定就是确定左上角和右上角
x2=x+c-1;
y2=y+c-1;
ans=s[x2][y2]-s[x-1][y2]-s[x2][y-1]+s[x-1][y-1];//计算边长为c的子矩阵的和
if(ans>maxx){
xx=x;yy=y;
maxx=ans;
}
}
}
printf("%d %d",xx,yy);
return 0;
}
F.光骓者的荣耀
分析:
他想要尽快完成城市的访问,但是只有一次传输的机会,所以这会儿就需要传输的路段最大,此时耗费的时间就会最小。
①首先先求前缀和,求出每个区间的前缀和的最大值→②用整个数组的前缀和减去某个区间的最大值
注意:
①这里的也是线段,所以线段数是n-1
②这道题的数据真的很大需要用longlong
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
long long a[N],s[N],ans=0;//不开longlong见祖宗,ai的数据比较大
int n,k;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n-1;i++){
scanf("%lld",&a[i]);
s[i]=s[i-1]+a[i];//前缀和
}
for(int i=0;i+k<n;i++){
ans=max(ans,s[k+i]-s[i]);
}
printf("%lld",s[n-1]-ans);
return 0;
}
总结:
①这里对所有的题目进行一个总结:需要用到前缀和以及差分的题目大多数是求某一个区间或者子矩阵求和或者对这个区间子矩阵进行加减而不改变其他的区域。
②同时可以发现前缀和和差分大多数伴随者max和min函数的使用,用得很多的是动态替换数值。