算法基础课-学习笔记1

一. 前缀和

题目1:前缀和

(1) 题目

点击一下,跳转到题目
在这里插入图片描述

(2) 答案

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], 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] + a[l] << endl;
    }
    return 0;
}

题目2:子矩阵的和

(1) 题目

点击一下,跳转到题目
在这里插入图片描述

(2) 思路

在这里插入图片描述

(3) 答案

#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N], s[N][N];
int n, m, q;
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][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j];
    
    while (q --)
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        int res = s[x2][y2] - s[x2][y1 - 1]- s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
        cout << res << endl;
    }
    return 0;
}

题目3:差分

(1) 题目

点击一下,跳转到题目
在这里插入图片描述

(2) 答案

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int n, m;
int main()
{
    cin >> n >> m;
    // 输入a[]
    for (int i = 1; i <= n; i ++) cin >> a[i];
    // 找到a[]对应的差分数组b[]
    for (int i = 1; i <= n; i ++) b[i] = a[i] - a[i - 1];
    // 对于a[]下标为l~r区间上每个数 + c,
    // 就等价于,在差分数组b[l] += c, b[r + 1] -= c,
    // 最后b[]累加起来就是a[]
    while (m --)
    {
        int l, r, c;
        cin >> l >> r >> c;
        b[l] += c;
        b[r + 1] -= c;
    }
    
    for (int i = 1; i <= n; i ++) a[i] = a[i - 1] + b[i];
    for (int i = 1; i <= n; i ++) cout << a[i] << ' ';
    return 0;
}

题目4:差分矩阵

(1) 题目

点击一下,跳转到题目
在这里插入图片描述

(2) 答案

#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N], s[N][N];
int n, m, q;
void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x2 + 1][y1] -= c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y2 + 1] += c;
}
int main()
{
    cin >> n >> m >> q;
    // a[]为原矩阵
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
            cin >> a[i][j];
            
    // 1. 找到a[]对应的差分矩阵b[]
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
            insert(i, j, i, j, a[i][j]);
    // 输出差分矩阵b[]
    // for (int i = 1; i <= n; i ++)
    // {
    //     for (int j = 1; j <= m; j ++) cout << b[i][j] << ' ';
    //     puts("");
    // }  
    // puts("");
    
    // 2. 对差分矩阵b[]进行加加减减操作
    while (q --)
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c);
    }
    
    // 3. 对差分矩阵b[]求和得到s[]矩阵
    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] + b[i][j];

    for (int i = 1; i <= n; i ++)
    {
        for (int j = 1; j <= m; j ++) cout << s[i][j] << ' ';
        puts("");
    }
    return 0;
}

二. 双指针算法(难)

题目1:最长连续不重复子序列

点击一下,跳转到题目

(1) 题目

在这里插入图片描述

(2) 答案

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N]; // s[]统计的是下标从j~i的元素出现的次数
int n;
int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> a[i];
    
    int res = 0;
    for (int i = 0, j = 0; i < n; i ++)
    {
        s[a[i]] ++; // 元素a[i]出现的次数++
        // 如果从下标j~i的数组中还有重复元素a[i]
        // 那就移动j往前且将a[j]--
        // (因为j前进时,a[j]已经不在下标为j~i对应的元素段了)
        while (j <= i && s[a[i]] > 1)
        {
            s[a[j]] --;
            j ++;
        }
        res = max(res, i - j + 1);
    }
    cout << res << endl;
    return 0;
}

题目2:数组元素的目标和

(1) 题目

(2) 答案

题目3:判断子序列

(1) 题目

(2) 答案

三. 位运算

题目:二进制中1的个数

点击一下,跳转到题目

(1) 题目

在这里插入图片描述

(2) 答案

#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> _10to2(int x)
{
    vector<int> num;
    while (x)
    {
        num.push_back(x % 2);
        x /= 2;
    }
    return num;
}
int main()
{
    cin >> n;
    while (n --)
    {
        int x;
        cin >> x;
        vector<int> num = _10to2(x);
        int res = 0;
        for (auto item : num)
            if (item) res ++;
        cout << res << ' ';
    }
    return 0;
}

四. 离散化(难)

题目:区间和

(1) 题目

在这里插入图片描述

(2) 解析

一段有意义的话:
离散化就是大而分散的一段段使用到的稀疏区间,整合映射到连续的一段较小的稠密区间里然后就可以通过普通前缀和公式来计算连续一段的区间和,本质上就是化大为小,把稀疏离散化简为稠密连续的一段。

主要步骤:

  1. 将要访问的下标x全部存到alls[]中,然后对alls[]中所有的元素进行排序、去重。
    那么!!!在这样一个有序(递增)数组中,对于离散的下标x可以采用二分法得到连续的下标i
  2. 先将要添加的元素(x, c)先存到add[]数组中,然后把这里面的元素插入到全新的数组a[]中;
    注意:对于下标x,值为c的元素:下标x要做个映射(二分法)到i
    假如说映射函数(二分法函数)为get_i,则 i = get_i(x); a[i] += c
  3. 现有数组为 a[],前缀和数组为s[],求l~r的元素和即为s[r] - s[l - 1]
    注意:lr都是alls[]中的元素,要映射成a[]中的下标
    在这里插入图片描述

(3) 答案

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 3e5 + 10;
typedef pair<int, int> PII;
vector<PII> add, query;
vector<int> alls;
int a[N], s[N];
int n, m;
int get_i(int x)
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l + 1;
}
int main()
{
    cin >> n >> m;
    /* 个人理解:
    区间和--离散化
    离散化就是把大而分散的一段段使用到的稀疏区间,
    整合映射到连续的一段较小的稠密区间里,
    然后就可以通过普通前缀和公式来计算连续一段的区间和,
    本质上就是化大为小,把稀疏离散化简为稠密连续的一段。
    */
    // 0. 将区间端点x及其上需要添加的数字c存到add中,将x加入到alls中
    while (n --)
    {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});
        alls.push_back(x);
    }
    // 0. 将访问的数组区间l~r存到query中和alls中
    while (m --)
    {
        int l, r;
        cin >> l >> r;
        query.push_back({l, r});
        alls.push_back(l);
        alls.push_back(r);
    }
    // 1. 离散化过程
    // (1)将alls数组中用到的下标(区间端点)进行排序,并去重
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    // (2)将之前add数组中的元素(x, c)要添加到a[]中的(i,c)上
    // 此处,注意:c不为0时,x是不连贯的,i是连贯的,
    // 我们要将alls中分散的下标x映射到a[]的下标i上
    for (auto item : add)
    {
        auto x = item.first, y = item.second;
        auto i = get_i(x);
        a[i] += y;
    }
    // 2. 回到了连续区间求前缀和的问题上了
    for (int i = 1; i <= alls.size(); i ++) s[i] = s[i - 1] + a[i];
    // 3. l~r上的元素之和就为s[r]-s[l-1],但是要记得将原来的l、r做个映射
    for (auto item : query)
    {
        auto x = item.first, y = item.second;
        auto l = get_i(x), r = get_i(y);
        cout << s[r] - s[l - 1] << endl;
    }
    return 0;
}

五. 区间合并(难)

题目:区间合并

(1) 题目

在这里插入图片描述

(2) 答案

答案1(更好理解,但是合并后的区间不好找了 )

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
vector<PII> segs;
int n;
int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++)
    {
        int l, r;
        cin >> l >> r;
        segs.push_back({l, r});
    }
    // 将区间们按照前端点进行排序,以便于下一步进行合并
    sort(segs.begin(), segs.end());
    
    int res = n;
    for (int i = 1; i < n; i ++)
    {
        // if 前一个区间和后一个区间有交集
        // 将后一个区间更新前后端点,
        // 前端点为前一个区间前端点,后端点为max(此区间后端点,前一个区间后端点)
        // else 啥也不干
        if (segs[i - 1].second >= segs[i].first)
        {
            res --;
            segs[i].second = max(segs[i].second,segs[i - 1].second);
            segs[i].first = segs[i - 1].first;
        }
    }
    cout << res << endl;
    return 0;
}

答案2(不好理解,但是合并后的区间都可以找到)

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
vector<PII> segs;
int n;
int main()
{
    cin >> n;
    while (n --)
    {
        int l, r;
        cin >> l >> r;
        segs.push_back({l, r});
    }
    sort(segs.begin(), segs.end());
    
    vector<PII> res;
    // st和ed分别维护当前区间的前后端点
    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
    {
        auto l = seg.first, r = seg.second;
        // if枚举区间前(左)端点<=当前区间后端点,则二者有交集,合并
        if (l <= ed)
            ed = max(r, ed);
        // else二者无交集,那就更新当前区间
        else
        {
            st = l;
            ed = r;
            res.push_back({st, ed});
        }
    }
    cout << res.size() << endl;
    // 可以看到合并后具体的区间
    // for (auto r : res)
    //     cout << r.first << ' ' << r.second << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值