一维前缀和
-
定义:原数组 a 1 、 a 2 、 a 3 . . . a_1、a_2、a_3... a1、a2、a3... 下标从1开始
前缀和 S i = a 1 + a 2 + . . . + a i S_i=a_1+a_2+...+a_i Si=a1+a2+...+ai S 0 = 0 S_0=0 S0=0
-
怎么求Si
for(i=1;i<=n;i++) S[i]=S[i-1]+a[i];
-
作用:求原数组中一段数的和
求[l,r]求这段区间中数的和,Sr — Sl-1
二维前缀和
-
定义:S[i, j] = 第 i 行 j 列格子左上部分所有元素的和
-
怎么求Si,j
for(i=1;i<=n;i++) for(j=1;j<=m;j++) S[i,j]=S[i-1,j]+S[i,j-1]-S[i-1,j-1]+a[i,j];
-
作用:以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
领地选择
题目链接:点这
题目描述:
作为在虚拟世界里统帅千军万马的领袖,小 Z 认为天时、地利、人和三者是缺一不可的,所以,谨慎地选择首都的位置对于小 Z 来说是非常重要的。首都被认为是一个占地 C×C 的正方形。小 Z 希望你寻找到一个合适的位置,使得首都所占领的位置的土地价值和最高。
输入格式:
第一行三个整数 N,M,C,表示地图的宽和长以及首都的边长。接下来 N 行每行 M 个整数,表示了地图上每个地块的价值。价值可能为负数。
输出格式:
一行两个整数 X,Y,表示首都左上角的坐标。
题目分析:
显然,这是一个二维前缀和问题,我们依次输入的N行M列数据就是原数组中每一个点的价值。而我们要求在 C×C 的面积里面最大的前缀和。
首先,我们求出利用前缀和公式求出Si,j
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] = S[i-1][j]+S[i][j-1]-S[i-1][j-1]+a[i][j];//求前缀和
}
}
然后,求从左上角的坐标(i,j)到右下角的坐标(i+C-1,j+C-1),面积为 C×C 的前缀和,注意i是从1到N-C+1,j是从1到M-C+1
for(int i=1;i<=N-C+1;i++)
{
for(int j=1;j<=M-C+1;j++)
{
sum=S[i+C-1][j+C-1]-S[i-1][j+C-1]-S[i+C-1][j-1]+S[i-1][j-1];//求领土价值
}
}
最后再比较sum和Max,更新Max,得到它的坐标即可。
我的答案如下
#include <iostream>
#include <stdio.h>
using namespace std;
const int Z=1010;
int a[Z][Z], S[Z][Z], sum, Max;
int N, M, C, x, y;
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] = S[i-1][j]+S[i][j-1]-S[i-1][j-1]+a[i][j];//求前缀和
}
}
for(int i=1;i<=N-C+1;i++)
{
for(int j=1;j<=M-C+1;j++)
{
sum=S[i+C-1][j+C-1]-S[i-1][j+C-1]-S[i+C-1][j-1]+S[i-1][j-1];//求领土价值
if(sum>Max)
{
Max=sum;
x=i;
y=j;
}
}
}
cout << x << " " << y ;
return 0;
}
连续自然数求和
题目链接:点这
题目描述:
对一个给定的自然数MM,求出所有的连续的自然数段,这些连续的自然数段中的全部数之和为MM。
例子:1998+1999+2000+2001+2002 = 10000,所以从1998到2002的一个自然数段为M=10000的一个解。
输入格式:
包含一个整数的单独一行给出M的值(10≤M≤2,000,000)。
输出格式:
每行两个自然数,给出一个满足条件的连续自然数段中的第一个数和最后一个数,两数之间用一个空格隔开,所有输出行的第一个按从小到大的升序排列,对于给定的输入数据,保证至少有一个解。
题目分析:
显然,这是一个一维前缀和的题目,先利用 S[i]=S[i-1]+i 求出S[i],然后求出[l,r]这段区间元素的前缀和,如果等于M,直接输出此时的l和r。
我的答案如下
#include <iostream>
#include <stdio.h>
using namespace std;
const int N=2e6+10;
int S[N], sum, M;
int main()
{
scanf("%d", &M);
for(int i=1;i<=M;i++)
S[i]=S[i-1]+i;
for(int i=1;i<M;i++)
{
for(int j=i;j<M;j++)
{
sum=S[j]-S[i-1];
if(sum==M)
printf("%d %d\n",i,j);
}
}
return 0;
}
股票买卖
题目链接:点这
题目描述:
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
输入格式:
第一行包含整数 N,表示数组长度。第二行包含 N 个不大于 109 的正整数,表示完整的数组。
输出格式:
输出一个整数,表示最大利润。
题目分析:
这道题解法多样,可以不用前缀和的方法解决。我是用贪心的做法,找到每个数前面最小的数,然后相减。比较减出来的结果,不断更新result,最后输出。
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int result = 0, Min = 0;
cin >> Min; //第一个数
while(-- n){
int num;
cin >> num;
if(Min > num) Min = num;
result = max(result, num - Min);
}
cout << result << endl;
return 0;
}
激光炸弹
题目链接:点这
题目描述:
地图上有 N 个目标,用整数Xi,Yi表示目标在地图上的位置,每个目标都有一个价值Wi。注意:不同目标可能在同一位置。现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和x,y轴平行。求一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式:
第一行输入正整数 N 和 R ,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。接下来N行,每行输入一组数据,每组数据包括三个整数Xi,Yi,Wi,分别代表目标的x坐标,y坐标和价值,数据用空格隔开。
输出格式:
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。
题目分析:
这道题很明显是一道二维前缀和,求区域最值问题,和领地选择有一定的相似之处,先将mx和my赋初值为R,然后输入每一个目标的坐标的时候,分别选mx和++x,my和++y中更大的值来更新mx和my的值,来算出区域的最大边界。
scanf("%d%d",&N,&R);
int mx=R;
int my=R;
while(N--)
{
scanf("%d%d%d",&x,&y,&w);
mx=max(mx,++x);
my=max(my,++y);
S[x][y]+=w;
}
然后就是利用前缀和求出面积为R*R的正方形,算出此区域的价值总和,不断更新sum,最后输出最大的sum即为所求。
我的答案如下:
#include<iostream>
#include<stdio.h>
using namespace std;
const int Z=5010;
int S[Z][Z], sum=0;
int N, R, x, y, w;;
int main(){
scanf("%d%d",&N,&R);
int mx=R;
int my=R;
while(N--)
{
scanf("%d%d%d",&x,&y,&w);
mx=max(mx,++x);
my=max(my,++y);
S[x][y]+=w;
}
for(int i=1;i<=mx;i++)
for(int j=1;j<=my;j++)
S[i][j] += S[i-1][j]+S[i][j-1]-S[i-1][j-1];
for(int i=R;i<=mx;i++)
for(int j=R;j<=my;j++)
sum=max(sum,S[i][j]-S[i-R][j]-S[i][j-R]+S[i-R][j-R]);
cout<<sum<<endl;
return 0;
}
借教室
题目链接:点这
#include <bits/stdc++.h> //万能头文件,但是不建议使用
using namespace std;
const int N = 1000010;
#define ll long long
ll cr[N],sum[N],b[N],c[N];
int n,m;
struct book{
int nums,start,end;
}book[N];
void insert(int l,int r,int c)
{
b[l]+=c; b[r+1]-=c;
}
bool check(int d)
{
for(int i=1;i<=n;i++) b[i] = c[i];
fill(sum+1,sum+n+1,0);
for(int i=1;i<=d;i++){
insert(book[i].start,book[i].end,-book[i].nums);
}
for(int i=1;i<=n;i++){
sum[i] = sum[i-1]+b[i];
if(sum[i]<0) return false;
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&cr[i]);
insert(i,i,cr[i]);
}
for(int i=1;i<=n;i++) c[i] = b[i];
for(int i=1;i<=m;i++) scanf("%d%d%d",&book[i].nums,&book[i].start,&book[i].end);
int l = 1; int r =m;
if(check(r)) { printf("0\n"); return 0;} //直接检查最后一个看是否满足要求
while(l<r){//我们要找的答案是返回false的,需要的是不满足的最小值
int mid = (l+r)/2;
if(!check(mid)) r = mid;
else l = mid+1;
}
printf("-1\n%d\n",l);
return 0;
}