算法基础
- 第一章:基础算法
- 1、排序
- 2、二分查找
- 3、大数据量的加法和减法(高精度加减法)
- 3.1、加法
- 3.2、减法
 
- 4、前缀和
- 4.1、一维前缀和
- 4.2、二维前缀和
 
- 5、差分
- 5.1、一维差分
- 5.2、二维差分
 
- 6、双指针
- 7、位运算
- 7.1、lowbit的应用
 
- 8、离散化
- 9、区间合并
 
- 第二章:数据结构
- 第三章:搜索与图论
- 第四章:数学知识
- 第五章:动态规划
- 第六章:贪心算法
第一章:基础算法
1、排序
- 各种排序算法
2、二分查找
- 描述:在一个有序数组里面查找某个目标值
- 解析: 
  - mid = left + (right - left)/2; 
    - mid所在的val小于target_val的时候,left = mid + 1,即区间取右半区间
- mid所在的val小于target_val的时候,right = mid - 1,即区间取左半区间
  
 
 
- mid = left + (right - left)/2; 
    
- 牛客题目链接:二分查找
- 代码
class BinarySearch {
public:
    int getPos(vector<int> A, int n, int val) {
        // write code here
        int left = 0,right = n-1;
        while(left <= right)
        {
            int mid = left + (right - left)/2;
            if(A[mid] == val)
            {
                auto i = mid - 1;
                for(;i>=0 && A[i] == val;i--)
                {
                }
                return i+1;
            }
            else if(A[mid] > val)
            {
                right = mid - 1;
            }
            else {
                left = mid + 1;
            }
        }
        return -1;
    }
};
3、大数据量的加法和减法(高精度加减法)
3.1、加法
-  描述:超出数据类型范围的数的相加 
-  解析: - 存储:为了方便进位,则把低位数保存在容器的低位
- 运算:对应位的和为(C+Ai+Bi)%10,进位为C/10;
- 全部算完之后还要判断最高位是否有进位,有的话把进位再插入容器
- 输出的时候逆序输出
  
 
-  牛客题目链接:高精度加法 
-  代码 
#include <iostream>
#include<string>
#include<vector>
using namespace std;
const int N = 10000;
void add(vector<int>&A, vector<int>&B, vector<int>&C)
{
    int t = 0;
    for(int i=0; i < A.size() || i < B.size(); i++)
    {
        if(i < A.size()) t += A[i];
        if(i < B.size()) t += B[i];
        C.push_back(t%10);
        t /= 10;
    }
    if(t > 0)
        C.push_back(t);
}
int main() {
    string a,b;
    cin >> a >> b;
    vector<int>A,B,C;
    for(int i = a.length()-1; i >= 0; i--)
    {
        A.push_back(a[i] - '0');
    }
    for(int i = b.length()-1; i >= 0; i--)
    {
        B.push_back(b[i] - '0');
    }
    add(A,B,C);
    for(int i = C.size()-1; i >= 0; i--)
    {
        printf("%d",C[i]);
    }
}
// 64 位输出请用 printf("%lld")
3.2、减法
-  描述:超出数据类型范围的数的减法 
-  解析:A>B则用A-B;A<B则用-(B-A),相减的时候为对应位相减且减去借位c,c起始为0,得到的结果分为大于0和小于0,大于0则直接得到输出结果,小于0则还需要加上10,两个可合并为(sum+10)%10,最后还要判断下一位的借位 
  
-  题目链接:未找到 
#include<iostream>
#include<vector>
#include<string>
using namespace std;
/* 比较两个数的大小,从高位往低位依次比较 */
bool cmp(vector<int>&A,vector<int>&B)
{
    if(A.size() != B.size())
    {
        return A.size() > B.size();
    }
    for(int i = A.size()-1; i >= 0; i--)
    {
        if(A[i] != B[i])
            return A[i] > B[i];
    }
    return true;
}
vector<int>sub(vector<int>&A,vector<int>&B)
{
    vector<int>C;
    for(int i = 0,c=0; i < A.size(); i++)
    {
    	/* c是借位 */
        c = A[i] - c;
        if(i < B.size())
            c -= B[i];
        C.push_back((c+10)%10);
        if(c < 0)
            c = 1;
        else
            c = 0;
    }
    /* 去掉前导0,全为0则保留最后一个0 */
    while(C.size() > 1 && C.back() == 0)
        C.pop_back();
    return C;
}
int main()
{
    string a,b;
    vector<int>A,B;
    cin >> a >> b;
    /* 低位数存入容器的低位位置 */
    for(int i = a.length()-1; i >= 0; i--)
    {
        A.push_back(a[i] - '0');
    }
    for(int i = b.length()-1; i >= 0; i--)
    {
        B.push_back(b[i] - '0');
    }
    
    if(cmp(A,B))
    {
        auto C = sub(A,B);
        for(int i = C.size()-1; i >= 0; i--)
        {
            printf("%d",C[i]);
        }
    }
    else
    {
        auto C = sub(B,A);
        printf("-");
        for(int i = C.size()-1; i >= 0; i--)
        {
            printf("%d",C[i]);
        }
    }
}
4、前缀和
4.1、一维前缀和
- 描述:在一个一维数组里面求某一段区间内的和
- 解析: 
  - 数组a[N],S[i] = S[i-1]+a[i],区间l到r的和为S[r]-S[l-1]
 
- 题目链接:前缀和
- 代码
#include <iostream>
#include<vector>
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N];
long long S[N];
int main() {
    int n,q;
    cin >> n >> q;
    for(int i = 1; i <= n; i++)
    {
       scanf("%d",&a[i]);
       S[i] = S[i-1] + a[i];
    }
    while(q--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%lld\n",S[r] - S[l-1]);
    }
    return 0;
}
// 64 位输出请用 printf("%lld")
4.2、二维前缀和
- 描述:一个二维矩阵A[n][m],求其(x1,y1)和其(x2,y2)之间的矩阵和
- 解析:先算出S[i][j],再算两点间的和
- 求S[i][j]的和
  
- 计算两点间的和,注意边界,需要包含x1,y1
  
- 题目链接:二维前缀和
- 代码
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int A[N][N];
long long S[N][N];
int main() {
    int n,m,q;
    cin >> n >> m >> q;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            cin >> A[i][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;
        cout << S[x2][y2] - S[x2][y1-1] - S[x1-1][y2] + S[x1-1][y1-1] << endl;
    }
}
5、差分
5.1、一维差分
- 描述:在a[n]这个数组中的某一段全部加上某个数
- 解析:an是bn的前缀和,bn是an的差分,在bl+k,在b(r+1)-k,相当于只在l到r这段里面的数加上了k
  
- 题目链接:一维差分
- 代码
#include <iostream>
using namespace std;
const int N = 100010;
int n,m;
long long a[N],b[N];
void insert(int l,int r,int k)
{
    b[l] += k;
    b[r + 1] -= k;
}
int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        insert(i, i, a[i]);
    }
       
    while(m--)
    {
        int l,r,k;
        cin >> l >> r >> k;
        insert(l, r, k);
    }
    for(int i = 1; i <= n; i++)
    {
        b[i] += b[i-1];
        cout << b[i] << " ";
    }
}
5.2、二维差分
- 描述:将二维矩阵中的其中一个子矩阵每个数都加上一个数
- 解析:只将图中的子矩阵中的每个数加上c,表达式如图中所示
  
- 同样b[i][j]是a[i][j]的差分,a是b的前缀和,也就是b[i][i]上方的子矩阵的和就为a[i][j],前缀和公式如图
  
- 题目链接:二维差分
- 代码
#include <iostream>
using namespace std;
const int N = 1010;
long long a[N][N],b[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;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            insert(i,j,i,j,a[i][j]);
        }
    while(q--)
    {
        int x1,y1,x2,y2,k;
        cin >> x1 >> y1 >> x2 >> y2 >> k;
        insert(x1,y1,x2,y2,k);
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
            cout << b[i][j] << " ";
        }
        cout << endl;
    }
}
6、双指针
- 题目链接:最长连续无重复子序列
- 代码
#include <unordered_map>
class Solution {
public:
    int maxLength(vector<int>& arr) {
        int n = arr.size();
        unordered_map<int, int>mp;
        int ret = 0;
        for(int i = 0,j = 0; i < n; i++)
        {
            mp[arr[i]]++;
            while(mp[arr[i]] > 1)
            {
                mp[arr[j]]--;
                j++;
            }
            ret = max(ret,i-j+1);
        }
        return ret;
    }
};
7、位运算
7.1、lowbit的应用
- 描述:求x的第k位的大小
- 公式:(x >> k) & (-x)
  
- 题目链接:二进制数中1的个数
8、离散化
-  描述:在一个无限长的坐标中进行添加和询问操作,坐标上的数起始全为0,add的(1,2)表示在坐标1加2,query的(1,2)表示求区间1,2之间的和,和为5,所以输出5 
  
-  解析:1、将操作的下标全部存进容器,排序后去重;2、使用二分查找算出离散化的值;3、再使用前缀和算出结果 
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int>PII;
const int N = 300010;
int n,m;
int a[N],S[N];
vector<int>alls;
vector<PII>add,query;
int find(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 r + 1;
}
int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i++)
    {
        int x,c;
        cin >> x >> c;
        add.push_back({x,c});
        alls.push_back(x);
    }
    for(int i = 0; i < m; i++)
    {
        int l,r;
        cin >> l >> r;
        query.push_back({l,r});
        alls.push_back(l);
        alls.push_back(r);
    }
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()),alls.end());
    for(auto item:add)
    {
        int x = find(item.first);
        a[x] += item.second;
    }
    for(int i=1;i<=alls.size();i++)
        S[i] = S[i-1] + a[i];
    for(auto item:query)
    {
        int l = find(item.first),r = find(item.second);
        cout << S[r] - S[l-1] << endl;
    }
    return 0;
}
9、区间合并
- 描述:给定多个区间,将能合并的合并,不能合并的保留,最后输出所有区间
- 解析:先将所有的区间排序,比较维护区间的右区间端点和新区间的左端点,如果新区间的左端点比维护区间的右端点大,则两个不能合并,将维护的区间保存起来,新区间变成维护区间接着往下比较,一直到最后一个
- 题目链接:区间合并
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int>PII;
vector<PII>store;
vector<PII>ret;
void merge(vector<PII>&tmp)
{
    int st = -2e9,ed = -2e9;
    sort(tmp.begin(),tmp.end());
    for(auto c:tmp)
    {
        if(ed < c.first)
        {
            if(st != -2e9) ret.push_back({st,ed});
            st = c.first,ed = c.second;
        }
        else
            ed = max(ed,c.second);
    }
    if(st != -2e9) ret.push_back({st,ed});
}
int main()
{
    int n,st,ed;
    cin >> n >> st >> ed;
    for(int i = 0; i < n; i++)
    {
        int l,r;
        cin >> l >> r;
        store.push_back({l,r});
    }
    store.push_back({st,ed});
    merge(store);
    /* 输出 */
    for(auto c:ret)
    {
        cout << c.first << c.second << endl;
    }
}



















