目录
2、K倍区间(第八届蓝桥杯省赛 C++ B组 & JAVA B/C组)
5、壁画(Google Kickstart2018 Round H Problem B)
6、递增三元组(第九届蓝桥杯省赛C++ B组 & JAVA B组)
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;
}