前缀和的基本概念
s[i] 为 数组a 前i个元素的和
s[i] = a[0]+ … + a[i]
s[i] = s[i-1] + a[i]
- l 到 r 区间内的和:
sum[l,r] = s[r]-s[l-1]
异或前缀和:
s[i] 为 数组a 前i个元素的异或和
s[i] = s[i-1] xor a[i]
二维前缀和
P2004 领地选择
题意理解:
有一个N x M的地图,地图上的每一个点都有一个值,求占地C x C 正方形里值的和最大
思路:
用sum[a][b]来记录 从[0][0]到[a][b]范围内的值之和,即二维前缀和。
边读边存
sum[i][j] += sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+data;
当读到的i,j >c 时。
开始判断以(i,j)为右下角坐标的C x C正方形的值和是不是最大的
即:
sumMax = max(sumMax,sum[i][j] - sum[i-c][j] - sum[i][j-c] + sum[i-c][j-c]);
知识点
如:求区间[a][b] —— [i][j]范围内的值 (i>a && j>b)
= sum[i][j] - sum[a][j] - sum[i][b] + sum[a][b]
code:
#include<stdio.h>
int sum[1009][1009];
int n,m,c;
int maxs=-1e9;
int maxi = 0;
int maxj = 0;
int main()
{
scanf("%d %d %d",&n,&m,&c);
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
int data;
scanf("%d",&data);
sum[i][j] += sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+data;
if(j>=c&&i>=c){
int now = sum[i][j] - sum[i-c][j] - sum[i][j-c] + sum[i-c][j-c];
if(now>maxs){
maxs = now;
maxi = i-c+1;
maxj = j-c+1;
}
}
}
}
printf("%d %d\n",maxi,maxj);
return 0;
}
由前缀和到差分
P3406 海底高铁
题意理解:
对于一条路线,要经过m个城市(可能有重复的城市),城市只有i-1 i i+1 相邻,对于相邻俩个城市i和i+1之间的路段,设走过这条路有ni次,要么付ci + ni * bi 要么付ai * ni;
就是一个选择问题,首先得知道每个路段会经过多少次。但是如果对于2号城市到10000号城市,每次都用循环去累加次数,会次数过多。
这里可以用到差分的思想。
对于l 到 r之间和
只需要记录ton[l] 和 ton[r]就行
即:
ton[l]++;
ton[r]–;
然后通过前缀和再求得中间的值:
for i = l+1;i<=r;i++
ton[l] = ton[l-1] + ton[i];
用p[i]来记录第i段路
code:
#include<stdio.h>
const int imax = 100009;
long long n,m;
long long p[imax];
long long a[imax],b[imax],c[imax];
int ton[imax];
long long ans = 0;
int main()
{
scanf("%d %d",&n,&m);
for(int i = 0;i < m;i++){
scanf("%d",&p[i]);
if(i!=0){
if(p[i-1]>=p[i]){
ton[p[i]]++;
ton[p[i-1]]--;
}
else{
ton[p[i-1]]++;
ton[p[i]]--;
}
}
}
for(int i = 1;i<n;i++){
scanf("%d %d %d",&a[i],&b[i],&c[i]);
ton[i] = ton[i-1]+ton[i];
if(ton[i]>0){
if(c[i] - (a[i]-b[i])*ton[i] <=0){
ans += (ton[i]*b[i]+c[i]);
// printf("对于第%d到%d,买卡,总共花了%d\n",i,i+1,(ton[i]*b[i]+c[i]));
}
else {
ans += ton[i]*a[i];
// printf("对于第%d到%d,不买卡,总共花了%d\n",i,i+1,ton[i]*a[i]);
}
}
}
printf("%lld",ans);
return 0;
}
分组的前缀和——与数学公式的结合
P2671 求和
题意理解:
有m个颜色总数,n个格子
求所有的俩元组(x,z)
- z = 2*i + x (i>=1)
- 并且x,z的颜色相同
(x+z) * (number_x +number_z)
的和
思路:
- nu数组 记录i对应的number
- c数据 记录i对应的颜色
- color[x][1] 记录i为奇数 且 颜色为x的格子数
- color[x][0] 记录i为偶数 且 颜色为x的格子数
- sum[x][1] 记录i为奇数 且 颜色为x的格子的number的和
- sum[x][0] 记录i为偶数 且 颜色为x的格子的number的和
分类讨论,对于颜色为x,i为奇数的格子
如果有n个,
那么就有
假设1 3 5 7格子的颜色相同
(1+3)*(number_1+number_3)+ (1+5) * (number_1+number_5) + (1+7) * (number_1+number_7)
(3+5) * (number_3+number_5) + (3+7) * (number_3+number_7)
(5+7) * (number_5+number_7)
从上面发现,我们可以把所有的项拆开来
可以发现
对于i*number_i
- 1出现3次
- 3出现3次
- 5出现3次
i*nui * (colorx-2)
对于i*number_j
- 1 3 1 5 1 7 …都是1次
i* sumx
再把i提出来
则对于i
i *(nui * (colorx-2) + sumx )
公式拆开发现并不是巧合,
则可以得到一个可以利用前缀和简化运算的操作:
for(i=1;i<=7;i+=2){
ans = ans + i * (nui * (colorx-2) )
}
code:
#include<stdio.h>
int n,m;
int sum[100009][2];
int color[100009][2];
int c[100009];
int nu[100009];
int ans = 0;
int main()
{
int x;
scanf("%d %d",&n,&m);
for(int i = 1;i<=n;i++)
{
scanf("%d",&x);
nu[i]=x;
}
for(int j = 1;j<=n;j++)
{
scanf("%d",&x);
c[j] = x;
color[x][j%2]++;
sum[x][j%2] = (sum[x][j%2] + nu[j])%10007; // 把所有这个颜色的格子数 按奇偶 加起来
}
for(int i = 1;i<=n;i++){
ans = (ans + i*(((color[c[i]][i%2]-2)*nu[i])%10007+sum[c[i]][i%2]))%10007;
}
printf("%d",ans);
return 0;
}