前缀和+模拟+归并排序

:总感觉刷题到了瓶颈期,有好多题目都需要反复思考才可以做出来,错误率也在增高,还是需要总结和巩固

前缀和

前缀和就是将数组的前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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值