【前缀和专题】&【蓝桥杯备考训练】:前缀和、K倍区间、子矩阵的和、统计子矩阵、壁画、递增三元组、激光炸弹 【已更新完成】

目录

1、前缀和(经典前缀和模板)

2、K倍区间(第八届蓝桥杯省赛 C++ B组 & JAVA B/C组)

3、子矩阵的和(经典模板)

4、统计子矩阵(第十三届蓝桥杯 省赛 C++ B组)

5、壁画(Google Kickstart2018 Round H Problem B)

6、递增三元组(第九届蓝桥杯省赛C++ B组 & JAVA B组)

7、激光炸弹(算法进阶指南)


1、前缀和(经典前缀和模板)

输入一个长度为 n 的整数序列。

接下来再输入 m 个询问,每个询问输入一对 l,r。

对于每个询问,输出原序列中从第 l个数到第 r 个数的和。

输入格式

第一行包含两个整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。

输出格式

共 m 行,每行输出一个询问的结果。

数据范围

1≤l≤r≤n,
1≤n,m≤100000
−1000≤数列中元素的值≤1000

输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
思路:

这就是一个经典的求前缀和问题,我们用递推公式s[i]=s[i-1]+a[i]处理即可

注意i要从1开始

代码:
#include<bits/stdc++.h>

using namespace std;

const int N= 1e5+5;

int a[N];
int s[N];

int n,m;

int main()
{
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    
    for(int i=1;i<=n;i++)
    {
        s[i]=s[i-1]+a[i];
    }//处理好前缀和
    
    while(m--)
    {
        int l,r;
        cin>>l>>r;
        
        cout<<s[r]-s[l-1]<<endl;
        
    }
    
    return 0;
}

2、K倍区间(第八届蓝桥杯省赛 C++ B组 & JAVA B/C组)

给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj+1 之和是 K 的倍数,我们就称这个区间 [i,j]是 K 倍区间。

你能求出数列中总共有多少个 K倍区间吗?

输入格式

第一行包含两个整数 N 和 K。

以下 N行每行包含一个整数 Ai。

输出格式

输出一个整数,代表 K 倍区间的数目。

数据范围

1≤N,K≤100000,
1≤Ai≤100000

输入样例:
5 2
1
2
3
4
5
输出样例:
6
思路:

直接暴力会超时,我们求出区间的前缀和后,对于前缀和数组进行分析:

区间[l,r]的和是k的倍数即(sum[r] - sum[l-1])%k == 0 即sum[r]%k == sum[l-1]%k

所以我们就可以考虑用哈希表存储,当相同的数相遇后res+=m【s[i]%k】,然后m【s[i]%k】++;

遍历一遍前缀和数组后输出答案

代码:
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

int n,k;

const int N=1e5+5;

int a[N];
LL s[N];

int m[N];

int main()
{
	cin>>n>>k;
	
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	
	for(int i=1;i<=n;i++)
	{
		s[i]=s[i-1]+a[i];
	}
	
	//开始处理区间
	LL res=0;

    m[0]++;//因为%0本来就是0,所以如果出现s[i]%k==0,为了符合题意,哈希表0的位置要提前加上1

	for(int i=1;i<=n;i++)
	{
		res+=m[s[i]%k];
		m[s[i]%k]++;
	} 
	cout<<res<<endl;
	return 0;
} 

3、子矩阵的和(经典模板)

输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2表示一个子矩阵的左上角坐标和右下角坐标。

对于每个询问输出子矩阵中所有数的和。

输入格式

第一行包含三个整数 n,m,q

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含四个整数 x1,y1,x2,y2表示一组询问。

输出格式

共 q 行,每行输出一个询问的结果。

数据范围

1≤n,m≤1000
1≤q≤200000,
1≤x1≤x2≤n
1≤y1≤y2≤m
−1000≤矩阵内元素的值≤1000

输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
思路:

与求前缀和思路相似,但要结合容斥原理

图解:

根据容斥原理容易得

矩阵求前缀和递推式:s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]

矩阵区域和递推式:aim=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1](aim表示的是从x1,y1到x2,y2区域的和(包含x1,y1和x2,y2))

代码:
#include<iostream>
using namespace std;
const int N=1010;
int n,m,q;
int a[N][N],s[N][N];
int main()
{
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>a[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    while(q--)
    {
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl;
    }
    return 0;
}

4、统计子矩阵(第十三届蓝桥杯 省赛 C++ B组)

给定一个 N×M 的矩阵 A,请你统计有多少个子矩阵 (最小 1×1,最大 N×M) 满足子矩阵中所有数的和不超过给定的整数 K?

输入格式

第一行包含三个整数 N,M 和 K。

之后 N 行每行包含 M 个整数,代表矩阵 A。

输出格式

一个整数代表答案。

数据范围

对于 30% 的数据,N,M≤20
对于 70% 的数据,N,M≤100
对于 100% 的数据,1≤N,M≤500;0≤Aij≤1000;1≤K≤2.5×1e8

输入样例:
3 4 10
1 2 3 4
5 6 7 8
9 10 11 12
输出样例:
19
样例解释

满足条件的子矩阵一共有 1919,包含:

  • 大小为 1×11×1 的有 1010 个。
  • 大小为 1×21×2 的有 33 个。
  • 大小为 1×31×3 的有 22 个。
  • 大小为 1×41×4 的有 11 个。
  • 大小为 2×12×1 的有 33 个。
思路:

也是一个矩阵前缀和问题

直接暴力肯定会超时,我们最后询问的时候采用双指针扫描的办法,用i,j代表左右边界

代码:
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=505;

//前缀和 
int s[N][N];

int main()
{
    //ios::sync_with_stdio(false);//没这个不行,或者用scanf
	int n,m,k;
	scanf("%d%d%d",&n,&m,&k);
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
            scanf("%d",&s[i][j]);
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
		}
		
	
			
	LL res=0;
	
	for(int i=1;i<=m;i++)//l i -> f j
		for(int j=i;j<=m;j++)//s维护上界,f维护下界 ,注意j=i!!!!!!
			for(int l=1,f=1;f<=n;f++)
			{
				while(l<=f && s[f][j]-s[l-1][j]-s[f][i-1]+s[l-1][i-1]>k)l++;
				if(l<=f)res+=f-l+1;
			}
	printf("%lld",res);
	return 0;
} 

5、壁画(Google Kickstart2018 Round H Problem B)

Thanh 想在一面被均分为 N 段的墙上画一幅精美的壁画。

每段墙面都有一个美观评分,这表示它的美观程度(如果它的上面有画的话)。

不幸的是,由于洪水泛滥,墙体开始崩溃,所以他需要加快他的作画进度!

每天 Thanh 可以绘制一段墙体。

在第一天,他可以自由的选择任意一段墙面进行绘制。

在接下来的每一天,他只能选择与绘制完成的墙面相邻的墙段进行作画,因为他不想分开壁画。

在每天结束时,一段未被涂颜料的墙将被摧毁(Thanh 使用的是防水涂料,因此涂漆的部分不能被破坏),且被毁掉的墙段一定只与一段还未被毁掉的墙面相邻。

Thanh 的壁画的总体美观程度将等于他作画的所有墙段的美观评分的总和。

Thanh想要保证,无论墙壁是如何被摧毁的,他都可以达到至少 B 的美观总分。

请问他能够保证达到的美观总分 B 的最大值是多少。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据的第一行包含整数 N。

第二行包含一个长度为 N 的字符串,字符串由数字 0∼90∼9 构成,第 i 个字符表示第 i 段墙面被上色后能达到的美观评分。

输出格式

每组数据输出一个结果,每个结果占一行。

结果表示为 Case #x: y,其中 x 为组别编号(从 11 开始),y为 Thanh 可以保证达到的美观评分的最大值。

数据范围

1≤T≤100
存在一个测试点N=5∗1e6,其他测试点均满足2≤N≤100

输入样例:
4
4
1332
4
9583
3
616
10
1029384756
输出样例:
Case #1: 6
Case #2: 14
Case #3: 7
Case #4: 31
样例解释

在第一个样例中,无论墙壁如何被破坏,Thanh都可以获得 66 分的美观总分。在第一天,他可以随便选一个美观评分3的墙段进行绘画。在一天结束时,第一部分或第四部分将被摧毁,但无论哪一部分都无关紧要。在第二天,他都可以在另一段美观评分 33 的墙段上作画。

在第二个样例中,Thanh 在第一天选择最左边的美观评分为 99 的墙段上作画。在第一天结束时唯一可以被毁掉的墙体是最右边的那段墙体,因为最左边的墙壁被涂上了颜料。在第二天,他可以选择在左数第二段评分为 55 的墙面上作画。然后右数第二段墙体被摧毁。请注意,在第二天,Thanh不能选择绘制第三段墙面,因为它不与任何其他作画墙面相邻。这样可以获得 1414 分的美观总分。

思路:

本题较长,也用到了博弈的思想,注意墙只能从两边坏,我们总是能涂满连续的【N/2】(上取整)面墙的,现在证明为什么:

假设我们从任意位置开始涂(假设天气极端聪明),然后我们墙从哪边坏一下我们就往哪边涂一下,按这样的方法我们总能涂到我们想要的(但必须是连续涂,不能跳着涂,并且墙也不会跳着坏),而且是涂满【N/2】(上取整)

于是我们求出前缀和后,从【N/2】(上取整)子数组中求最大值

代码:
//一开始没想到可以天气坏那边画哪边,不相信是[n/2]上取整的子数组中的最大值就是答案
#include<bits/stdc++.h>

using namespace std;

int t,n;

const int N=5*1e6+5;

char a[N];
int s[N];

int main()
{
	cin>>t;
	int c=1;
	while(t--)
	{
		
		//cout<<"-------"<<endl;
		
		int m;
		cin>>m;
		
		for(int i=1;i<=m;i++)
		{
			cin>>a[i];
		}
		
		//cout<<"-------"<<endl;
		
		for(int i=1;i<=m;i++)
		{
			s[i]=s[i-1]+(a[i]-'0');
		}
		
		int cnt=(m+1)/2;
		int maxs=-1;
		
		for(int i=1;i<=m-cnt+1;i++)
		{
			int j=i+cnt-1;//注意要减去1,因为直接加上会导致多一块壁画 
			maxs=max(maxs,s[j]-s[i-1]);
		}
		cout<<"Case #"<<c<<": "<<maxs<<endl;
		c++;
	}
	
	return 0;
}

6、递增三元组(第九届蓝桥杯省赛C++ B组 & JAVA B组)

给定三个整数数组

A=[A1,A2,…AN]
B=[B1,B2,…BN]
C=[C1,C2,…CN]

请你统计有多少个三元组 (i,j,k)满足:1≤i,j,k≤N    Ai<Bj<Ck

输入格式

第一行包含一个整数 N

第二行包含 N 个整数 A1,A2,…AN

第三行包含 N个整数 B1,B2,…BN

第四行包含 N个整数 C1,C2,…CN

输出格式

一个整数表示答案。

数据范围

1≤N≤1e5
0≤Ai,Bi,Ci≤1e5

输入样例:
3
1 1 1
2 2 2
3 3 3
输出样例:
27
思路:

和第二题比较相似,同样同时用到了前缀和和哈希表

我们只需要处理 A 和 C,然后枚举B即可,并且用一个数组si【x】存储 i 组中小于x数组的个数

代码:
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=1e5+5;

int n;

int a[N],b[N],c[N];
//求前缀和后就能知道记录前n大的数有几个 
int ma[N],mc[N];
int sa[N],sc[N];

int main()
{
	cin>>n;
	
	//读入A 
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]); 
		ma[a[i]]++;
	}
	
	sa[0]=ma[0];//0的个数也不能忽略
	
	//计算A组前缀和 
	//sa[i]存的是小于等于i的个数
	for(int i=1;i<N;i++)sa[i]=sa[i-1]+ma[i];
	
	
	//读入b
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);
	 
	//处理c 
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&c[i]);
		mc[c[i]]++;
	}
	
	sc[0]=mc[0];//0的个数也不能忽略
	
	//计算A组前缀和 
	//sc[i]存的是小于等于i的个数
	for(int i=1;i<N;i++)sc[i]=sc[i-1]+mc[i];
	
	//询问B
	LL res=0;
	for(int i=1;i<=n;i++)
	{
	    //cout<<sc[11];
		int t =b[i];
		//cout<<t<<endl;
		//cout<<sa[t-1];
		//cout<<sc[N-1]<<endl;
		res+=(LL)sa[t-1]*(sc[N-1]-sc[t]);
	}
	
	cout<<res;
	return 0;
} 

7、激光炸弹(算法进阶指南)

地图上有 N 个目标,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式

第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形包含的横纵位置数量,数据用空格隔开。

接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。

输出格式

输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围

0≤R≤1e9
0<N≤10000
0≤Xi,Yi≤5000
0≤Wi≤1000

输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
思路:

也是一个枚举,处理好矩阵前缀和后,我们用一个边长为r*r的正方形枚举矩阵中的所有情况,注意题目中的r最大是1e9,我们的矩阵边长最大才5000,所以要用min()来取一个合适的r,并且要注意好避免Segmentation Fault,注意最后枚举的时候我们的边界是N,而不是以前题目中给的n了,所以i和j应该是<N,而不是<=N(之前我们题目中都是<=n),不然会segmentation fault

代码:
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=5003;

int s[N][N];

int n,m;

int main()
{
	cin>>n>>m;// m * m 是炸弹的范围 
	
	m=min(5002,m);
	//cout<<"____"<<endl;
	for(int i=1;i<=n;i++)
	{
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		//偏移一下,更好计算
		s[++x][++y]+=w;//注意是  "+="  !,因为有可能重复目标有多个价值 
	}
	//cout<<"____"<<endl;
	for(int i=1;i<N;i++)
		for(int j=1;j<N;j++)
		{
			s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+s[i][j];
		}
	
	int res=0;
	//cout<<"____"<<endl;
	for(int i=m;i<N;i++)
		for(int j=m;j<N;j++)
		{
			res=max(res,s[i][j]-s[i-m][j]-s[i][j-m]+s[i-m][j-m]);	
		}
	
	printf("%d",res);
	return 0;
}

  • 21
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值