前缀和常见的三种情况

前缀和的基本概念

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值