:总感觉刷题到了瓶颈期,有好多题目都需要反复思考才可以做出来,错误率也在增高,还是需要总结和巩固
前缀和
前缀和就是将数组的前n项和存在另一个数组中,以方便计算数组区间和的计算
1.前缀和基础题:
eg:输入一个长度为 n的整数序列。接下来再输入 m个询问,每个询问输入一对 l,r。对于每个询问,输出原序列中从第 l个数到第 r个数的和。
求解思想:因为要求第l到第r个数的和,所以就可以想到前缀和,也就是前r个数的和减去前l个数的和就是第l到第r个数的和
代码:
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 100010;
int n,m;
int a[N];//表示原数组
int s[N];//表示前缀和数组
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
s[i] = s[i-1]+a[i];//存储前缀和
}
while(m--){
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",s[r]-s[l-1]);//因为是l-》r所以不能把l的减去
}
return 0;
}
2.二维前缀和:
eg:输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
基本思想:与一维前缀和不同的是,二维前缀和再求子区间时,需要加上叠加块的值,也就是红蓝交替的地方
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int n,m,q;
int a[N][N],s[N][N];
int main(){
scanf("%d%d%d",&n,&m,&q);
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];
}
}
while(q--){
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
//子矩阵,需要加上多减去的重复块
printf("%d\n",s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]);
}
return 0;
}
模拟
1.连号区间数
eg:小明这些天一直在思考这样一个奇怪而有趣的问题在 1∼N 的某个排列中有多少个连号区间呢?这里所说的连号区间的定义是:如果区间 [L,R] 里的所有元素(即此排列的第 L个到第 R 个元素)递增排序后能得到一个长度为 R−L+1 的“连续”数列,则称这个区间连号区间。当 N 很小的时候,小明可以很快地算出答案,但是当 N 变大的时候,问题就不是那么简单了,现在小明需要你的帮助。
基本思想:连续区间的序号也必连续,eg:413256,4132的序号也是连续的
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010,INF = -10000000;
int n,a[N];
int main(){
cin>>n;
for(int i=0;i<n;i++){//枚举区间左端点
int minv = INF,maxv= -INF;
for(int j=i;j<n;j++){
//枚举区间右端点
minv = min(minv,a[j]);
maxv = max(maxv,a[j]);
if(maxv-minv == j-i)res++;//连续区间的序号也必连续
}
}
cout<<res<<endl;
}
2.递增三元组
eg:给定三个整数数组
A=[A1,A2,…AN],
B=[B1,B2,…BN],
C=[C1,C2,…CN],
请你统计有多少个三元组 (i,j,k) 满足:
1≤i,j,k≤N
Ai<Bj<Ck
基本思想:一种就是暴力枚举,枚举所有的情况,第二种就是只枚举b,找比b小的就是a,比b大的就是c
代码:
#include [HTML_REMOVED]
include [HTML_REMOVED]
include [HTML_REMOVED]
include [HTML_REMOVED]
using namespace std;
const int N = 1050;
typedef long long LL;
int main() {
int n;
cin >> n;
int a[N] = { 0 }, b[N] = { 0 }, c[N] = { 0 };
int as[N] = { 0 };//as[i]表示在A[]中有多少个数小于b[i]
int cs[N] = { 0 };//cs[i]表示在C[]中有多少个数大于b[i]
int cnt[N] = {0}, s[N] = { 0 };//cnt统计次数
for (int i = 1; i <= n; i++)cin>>a[i];
for (int i = 1; i <= n; i++)cin >>b[i];
for (int i = 1; i <= n; i++)cin >>c[i];
//求as[]
for (int i = 1; i <= n; i++)cnt[a[i]]++;//求a[i]每个元素出现的次数
for (int i = 1; i < N; i++) {
s[0] = 0;
s[i] = s[i - 1] + cnt[i];
};//求a[i]的前缀和,ps(不是a[i]的)
for (int i = 1; i <= n; i++)as[i] = s[b[i] - 1];//as[i]表示在A[]中有多少个数小于b[i]
memset(cnt, 0, sizeof cnt);
memset(s, 0, sizeof s);
//求cs[]
for (int i = 1; i <= n; i++)cnt[c[i]]++;//求c[i]每个元素出现的次数
for (int i = 1; i < N; i++) {
s[0] = 0;
s[i] = s[i - 1] + cnt[i];
};//求c[i]的前缀和
for (int i = 1; i <= n; i++)cs[i] = s[N - 1] - s[b[i]];//cs[i]表示在c[]中有多少个数小于b[i]
//枚举每个b[i]
LL res = 0;
for (int i = 1; i <= n; i++)
res += (LL)as[i] * cs[i];//a*c就是枚举的总次数
cout << res;
return 0;
}
3.特别的数:
eg:小明对数位中含有 2、0、1、9的数字很感兴趣(不包括前导 0
),在 1 到 40 中这样的数包括 1、2、9、10 至 32、39 和 40,共 28 个,他们的和是 574。请问,在 1 到 n 中,所有这样的数的和是多少?
基本思想:遍历数字,找到每个数字是要求数字的,然后次数+1
常用小技巧:关于取出x的每位数字 和 将字符数字转为数字
1.取出x的每位数字
int t = x % 10;
x /= 10;
2.将字符数字转为数字
int x = 0;
for (int i = 0; i < str.size(); i ++ )
x = x * 10 + str[i] - ‘0’;
4.错误票据
eg:某涉密单位下发了某种票据,并要在年终全部收回。每张票据有唯一的ID号。全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。你的任务是通过编程,找出断号的ID和重号的ID。假设断号不可能发生在最大和最小号。
基本思想:此题考查的是不知道输入数据大小的情况下,如何进行输入的问题
代码一:
cin版
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5+5, INF = 0x3f3f3f3f;
int ha[N];
int main()
{
int n;
cin >> n;//这个读入没什么用
int minv = INF, maxv = -INF;
int tp;
while(cin >> tp)//直接读到文件尾部停止
{
if(tp < minv) minv = tp;
if(tp > maxv) maxv = tp;
ha[tp] ++;
}
int ans1 = 0, ans2 = 0;
for(int i = minv; i <= maxv; ++ i)
{
if(ha[i] == 0) ans1 = i;
if(ha[i] == 2) ans2 = i;
}
cout << ans1 << " " << ans2 << endl;
}
代码二
scanf版
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M = 100001;
int cnt[M];
int main() {
int line, Min = 100001, Max = 0, a;
int n, m; //m表示断号ID,n表示重号ID
cin >> line;
while(scanf("%d",&a)!=EOF){
cnt[a]++;
Max = max(Max, a);
Min = min(Min, a);
}
for (int j = Min; j <= Max; j++) {
if (cnt[j] == 0) m = j;
else if (cnt[j] == 2) n = j;
}
printf("%d %d",m,n);
return 0;
}
5.移动距离
要记住两个距离
曼哈顿距离(折线距离):|x1-x2|+|y1-y2|
欧几里得距离(直线距离):sqrt(x12-x22+y12+y22)
6.日期问题
eg:小明正在整理一批历史文献。这些历史文献中出现了很多日期。小明知道这些日期都在1960年1月1日至2059年12月31日。令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在很多可能的日期与其对应。比如02/03/04,可能是2002年03月04日、2004年02月03日或2004年03月02日。
给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?
基本思想:单个枚举月日年,年月日,日月年这样太麻烦,不如直接枚举年份和具体的日期,看是否符合
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
bool check_valid(int year,int month,int day) {
if (month==0||month > 12)return false;
if (day == 0)return false;
if (month!=2)
{
if (day>days[month])
{
return false;
}
}
else
{
int leap = year % 100 && year % 4 == 0 || year % 400 == 0;
//这里如果是闰年就会返回1,平年返回0
if (day>28+leap)
{
return false;
}
}
return true;
}
int main() {
int a, b, c;
scanf("%d/%d/%d",&a,&b,&c);
//枚举具体的日期,看是否符合要求
for (int date = 19600101; date <= 20591231; date++)
{
int year = date / 10000, month = date % 10000 / 100, day = date % 100;
if (check_valid(year, month, day)) {
if (year%100==a&&month==b&&day==c||//年月日
month==a&&day==b&&year%100==c||//月日年
day==a&&month==b&&year%100==c)//日月年
{
printf("%d-%02d-%02d\n",year,month,day);
}
}
}
return 0;
}
归并排序
采用分支的思想,具体见注释
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100000;
int a[N],temp[N];//a原始数组,temp临时数组
void mergesort(int l,int r) {
//递归边界
if (l >= r)return;//说明递归到底了
//步骤1.确立分界点mid
int mid = l + r >> 1;//=>(l+r)/2
//步骤2.递归左右区间
mergesort(l, mid), mergesort(mid+1,r);
//步骤3.使用两个指针指向数组头(左右区间的)
int i = l, j = mid + 1;
//临时数组的数组头
int k = l;
//比较q[i],q[j],左右区间的循环中止条件,到头了
while (i<=mid&&j<=r)
{
//第一种情况q[i]<=q[j],则temp[k++]=q[i++],使用临时数组记录结果
if (a[i]<=a[j])
{
temp[k++] = a[i++];
}
else
{
temp[k++] = a[j++];
}
}
//步骤4.如果左区间还有元素
while (i<=mid)
{
temp[k++] = a[i++];
}
//步骤4.如果右区间还有元素
while (j <= r)
{
temp[k++] = a[j++];
}
//步骤5.复制回原数组
for (int i = l; i <=r ; i++)
{
a[i] = temp[i];
}
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
mergesort(0,n-1);
for (int i = 0; i < n; i++)
{
cout<< a[i]<<" ";
}
return 0;
}