算法知识点汇总

知识点

STL

vector, 变长数组,倍增的思想
    size()  返回元素个数
    empty()  返回是否为空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    支持比较运算,按字典序

pair<int, int>
    first, 第一个元素
    second, 第二个元素
    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

string,字符串
    size()/length()  返回字符串长度
    empty()
    clear()
    substr(起始下标,(子串长度))  返回子串
    c_str()  返回字符串所在字符数组的起始地址

queue, 队列
    size()
    empty()
    push()  向队尾插入一个元素
    front()  返回队头元素
    back()  返回队尾元素
    pop()  弹出队头元素

priority_queue, 优先队列,默认是大根堆
    size()
    empty()
    push()  插入一个元素
    top()  返回堆顶元素
    pop()  弹出堆顶元素
    定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;

stack,size()
    empty()
    push()  向栈顶插入一个元素
    top()  返回栈顶元素
    pop()  弹出栈顶元素

deque, 双端队列
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
    []
        
哈希表
#include<unordered_map>
unordered_map<int, int> map;
unordered_map<int, int> hmap{ {1,10},{2,12},{3,13} };
hmap[4] = 14;
hmap[4] = 15;
cout << hmap[4];  //结果为15
hmap.insert({ 5,15 });
iter = hmap.find(2);
int count = hmap.count(key);

单调栈

//常见模型:找出每个数左边离它最近的比它大/小的数
#include<iostream>

using namespace std;

const int N = 1e5 + 10;
int n;
int stk[N], tt;

int main()
{
    cin >> n;
    for(int i = 0; i < n; i++)
    {
        int x; 
        cin >> x;
        while(tt && stk[tt] >= x) tt--;
        if(tt) cout << stk[tt] << " ";
        else cout << -1 << " ";
        stk[++tt] = x;
    }
    return 0;
}

单调队列

链接: link

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;

const int N = 1000010;
int a[N];
int main()
{
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];//读入数据
    deque<int> q;
    for(int i = 1; i <= n; i++)
    {
        while(q.size() && q.back() > a[i]) //新进入窗口的值小于队尾元素,则队尾出队列
            q.pop_back();
        q.push_back(a[i]);//将新进入的元素入队
        if(i - k >= 1 && q.front() == a[i - k])//若队头是否滑出了窗口,队头出队 
            q.pop_front();
        if(i >= k)//当窗口形成,输出队头对应的值
            cout << q.front() <<" ";
    }
    q.clear();
    cout << endl;

    //最大值亦然
    for(int i = 1; i <= n; i++)
    {
        while(q.size() && q.back() < a[i]) q.pop_back();
        q.push_back(a[i]);
        if(i - k >= 1 && a[i - k] == q.front()) q.pop_front(); 
        if(i >= k) cout << q.front() << " ";

    }
}

输出随机数

#include<stdlib.h>
#include<time.h>
//以上两个头文件必须加
srand(time(NULL));
//输出随机数前执行此语句
printf("%d",rand()%X);
//输出一个0~X-1的随机整数。

if(是样例) printf(样例);
else printf("-1");

单起点单终点最短路

Dijkstra-朴素O(n^2)
初始化距离数组, dist[1] = 0, dist[i] = inf;
for n次循环 每次循环确定一个min加入S集合中,n次之后就得出所有的最短距离
将不在S中dist_min的点->t
t->S加入最短路集合
用t更新到其他点的距离

Bellman_fordO(nm)
注意连锁想象需要备份, struct Edge{inta,b,c} Edge[M];
初始化dist, 松弛dist[x.b] = min(dist[x.b], backup[x.a]+x.w);
松弛k次,每次访问m条边

Floyd O(n^3)
初始化d
k, i, j 去更新d

dijkstra

链接: link

//伪代码
int dist[n],state[n];
dist[1] = 0, state[1] = 1;
for(i:1 ~ n)
{
    t <- 没有确定最短路径的节点中距离源点最近的点;
    state[t] = 1;
    更新 dist;
}


#include<iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510, M = 100010;

int h[N], e[M], ne[M], w[M], idx;//邻接表存储图
int state[N];//state 记录是否找到了源点到该节点的最短距离
int dist[N];//dist 数组保存源点到其余各个节点的距离
int n, m;//图的节点个数和边数

void add(int a, int b, int c)//插入边
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void Dijkstra()
{
    memset(dist, 0x3f, sizeof(dist));//dist 数组的各个元素为无穷大
    dist[1] = 0;//源点到源点的距离为置为 0
    for (int i = 0; i < n; i++)
    {
        int t = -1;
        for (int j = 1; j <= n; j++)//遍历 dist 数组,找到没有确定最短路径的节点中距离源点最近的点t
        {
            if (!state[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        }

        state[t] = 1;//state[i] 置为 1。

        for (int j = h[t]; j != -1; j = ne[j])//遍历 t 所有可以到达的节点 i
        {
            int i = e[j];
            dist[i] = min(dist[i], dist[t] + w[j]);//更新 dist[j]
        }


    }
}

int main()
{
    memset(h, -1, sizeof(h));//邻接表初始化

    cin >> n >> m;
    while (m--)//读入 m 条边
    {
        int a, b, w;
        cin >> a >> b >> w;
        add(a, b, w);
    }

    Dijkstra();
    if (dist[n] != 0x3f3f3f3f)//如果dist[n]被更新了,则存在路径
        cout << dist[n];
    else
        cout << "-1";
}

bellmen_ford

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510, M = 10010;

struct Edge {
    int a;
    int b;
    int w;
} e[M];//把每个边保存下来即可
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边

int bellman_ford() {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < k; i++) {//k次循环
        memcpy(back, dist, sizeof dist);
        for (int j = 0; j < m; j++) {//遍历所有边
            int a = e[j].a, b = e[j].b, w = e[j].w;
            dist[b] = min(dist[b], back[a] + w);
            //使用backup:避免给a更新后立马更新b, 这样b一次性最短路径就多了两条边出来
        }
    }
     return dist[n];

}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < m; i++) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        e[i] = {a, b, w};
    }
    int res = bellman_ford();
    if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");
    else cout << res;

    return 0;
}

多源最短路

floyd()

#include <iostream>
using namespace std;

const int N = 210, M = 2e+10, INF = 1e9;

int n, m, k, x, y, z;
int d[N][N];

void floyd() {
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main() {
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            if(i == j) d[i][j] = 0;
            else d[i][j] = INF;
    while(m--) {
        cin >> x >> y >> z;
        d[x][y] = min(d[x][y], z);
        //注意保存最小的边
    }
    floyd();
    while(k--) {
        cin >> x >> y;
        if(d[x][y] > INF/2) puts("impossible");
        //由于有负权边存在所以约大过INF/2也很合理
        else cout << d[x][y] << endl;
    }
    return 0;
}

最小生成树

链接: link

Kruskal

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 100010;
int p[N];//保存并查集

struct E{
    int a;
    int b;
    int w;
    bool operator < (const E& rhs){//通过边长进行排序
        return this->w < rhs.w;
    }

}edg[N * 2];
int res = 0;

int n, m;
int cnt = 0;
int find(int a){//并查集找祖宗
    if(p[a] != a) p[a] = find(p[a]);
    return p[a];
}
void klskr(){
    for(int i = 1; i <= m; i++)//依次尝试加入每条边
    {
        int pa = find(edg[i].a);// a 点所在的集合
        int pb = find(edg[i].b);// b 点所在的集合
        if(pa != pb){//如果 a b 不在一个集合中
            res += edg[i].w;//a b 之间这条边要
            p[pa] = pb;// 合并a b
            cnt ++; // 保留的边数量+1
        }
    }
}
int main()
{

    cin >> n >> m;
    for(int i = 1; i <= n; i++) p[i] = i;//初始化并查集
    for(int i = 1; i <= m; i++){//读入每条边
        int a, b , c;
        cin >> a >> b >>c;
        edg[i] = {a, b, c};
    }
    sort(edg + 1, edg + m + 1);//按边长排序
    klskr();
    //如果保留的边小于点数-1,则不能连通
    if(cnt < n - 1) {
        cout<< "impossible";
        return 0;
    }
    cout << res;
    return 0;
}

树状数组和线段树

树状数组 (logn)

  1. 给某个位置上的数加上一个数 ----- 单点修改
  2. 快速的求动态的前缀和 ------ 区间查询
    延申 ----- 区间修改和单点查询 + 差分
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int a[N], tr[N];

int lowbit(int x)
{
    return x & -x;
}

void add(int x, int v)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += v;
}

int query(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i ++ ) add(i, a[i]);

    while (m -- )
    {
        int k, x, y;
        scanf("%d%d%d", &k, &x, &y);
        if (k == 0) printf("%d\n", query(y) - query(x - 1));
        else add(x, y);
    }

    return 0;
}

线段树

  1. 单点修改
  2. 区间查询
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int w[N];
struct Node
{
    int l, r;
    int sum;
}tr[N * 4];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[r]};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if (l <= mid) sum = query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

void modify(int u, int x, int v)
{
    if (tr[u].l == tr[u].r) tr[u].sum += v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);

    int k, a, b;
    while (m -- )
    {
        scanf("%d%d%d", &k, &a, &b);
        if (k == 0) printf("%d\n", query(1, a, b));
        else modify(1, a, b);
    }

    return 0;
}

字符串哈希

#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5+5,P = 131;//131 13331
ULL h[N],p[N];

// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或  13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
ULL query(int l,int r){
    return h[r] - h[l-1]*p[r-l+1];
}
int main(){
    int n,m;
    cin>>n>>m;
    string x;
    cin>>x;

    //字符串从1开始编号,h[1]为前一个字符的哈希值
    p[0] = 1;
    h[0] = 0;
    for(int i=0;i<n;i++){
        p[i+1] = p[i]*P;            
        h[i+1] = h[i]*P +x[i];      //前缀和求整个字符串的哈希值
    }

    while(m--){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(query(l1,r1) == query(l2,r2)) printf("Yes\n");
        else printf("No\n");

    }
    return 0;
}

1. 求二进制中1的个数

链接: 二进制中1的个数

int get_count(int x)//返回x的二进制有多少个1
int get_count(int x)
{
    int res = 0;
    while (x)
    {
        res ++ ;
        x -= x & -x;
    }
    return res;
}

2. 建树,树的DFS, BFS

链接: 排列数字
链接: 走迷宫
链接: 树的重心
链接: 图中点的层次

记得初始化头节点

const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;

void add(int a, int b)  //如果是无向图,加两条边
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int dfs(int u)
{
	state[u] = true;
	for(int  i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(!state[j])
			dfs(j);
	}
}

queue<int> q;
st[1] = true; //表示1号点已经被遍历过了
q.push(1);

while(q.size())
{
	int t = q.front();
	q.pop();
	for(int i = h[t]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(!st[j])
		{
			st[j] = true;
			q.push(j);
		}
	}
}

3. 快速幂 O(logk)

链接: 快速幂

用来快速求出ak mod p的结果
数据范围: 1 <= a, p, k <= 109

//两个十的九次方数相乘会爆int
typedef long long LL;
int qmi(int a, int k, int p)
{
	int res = 1;
	while(k)
	{
		if(k & 1) res = (LL)res * a % p; //要先转型再计算
		k >>= 1;
		a = (LL)a * a % p;
	}
	return res;
}

4. 分解质因数 O(sqrt(n))

链接: 分解质因数

void divide(int x)
{
	for(int i = 2; i * i <= x; i++)  //x > 2 * 10^10的范围太大的话,i要定义成LL(9 * 10^19)
		if(x % i == 0)
		{
			int s = 0;
			while(x % i == 0) x /= i, s++;
			cout << i << " " << s << endl;
		}
	//大于根号x的数只能有一个,此时x也是质因子
	if(x > 1) cout << x << " " << 1 << endl; 
	cout << endl;
}		

5. 欧拉函数

链接: 欧拉函数
在这里插入图片描述

int phi(int x)
{
	int res = x;
	for(int i = 2; i * i <= n; i++)
		if(x % i == 0)
		{
			while(x % i == 0) x /= i;
			res = res / i * (i - 1);
		}
	if(x > 1) res = res / x * ( x - 1 );
	return res;
}

6. 最大公约数 O(n(log(n))

链接: 最大公约数

//辗转相除法
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}
//辗转相减法

lcm最小公倍数

最小公倍数 = 两数乘积 / 最大公约数

int gcd(int a, int b){
    do{
        int c = a % b;
        a = b;
        b = c;
    }while(b);
    return a;
}
int lcm(int a, int b){
    return a*b / gcd(a, b);
}

唯一分解定理

在这里插入图片描述

约数定理

约数个数定理在这里插入图片描述

约数和定理

在这里插入图片描述

7. 二分 O(nlog(n))

链接: 数的范围

二分的作用是找到一个数是答案。
二分将区间分为两个部分,一边成立,一边不成立。

二分右半边的最左侧答案

int bsearch_1(int l, int r)
{
	while(l < r)
	{
		int mid = l + r >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	return l;
}

二分左半边的最右侧答案

int bsearch_2(int l, int r)
{
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if(check(mid)) l = mid;
		else r = mid - 1;
	}
	return l;
}

8. 差分 O(nlog(n))

链接: 差分矩阵
连续子区间相同变化。

for(int i = 1; i <= n; i++)
{
    cin >> a[i];
    b[i] = a[i] - a[i - 1];
}
for(int i = 1; i <= m; i++)
{
    int l, r, c;
    cin >> l >> r >> c;
    b[l] += c;
    b[r + 1] -= c;
}
for(int i = 1; i <= n; i++)
{
    b[i] += b[i - 1];
}

9. 前缀和 O(nlog(n))

链接: 子矩阵的前缀和
求一段子区间上的和

for(int i = 1; i <= n; i++)
{
    cin >> a[i];
    a[i] += a[i - 1]; 
}
while(m--)
{
    int l, r;
    cin >> l >> r;
    cout << a[r] - a[l - 1] << endl;
}

10. 双指针 O(log(n))

链接: 最长连续不重复子序列
双指针的本质是:i,j具有单调关系,从而i指针遍历的时候,j指针不需要回退。

//通用模板
for(int i = 0, j = 0; i < n; i++)
{
	while(j < i && check(i, j)) j++;
	... //每道题的具体逻辑
}

11. 日期问题

链接: 日期差值

//基础模板
//每个月有几天
int months[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
//判断闰年
int is_leap(int year)
{
    if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
        return 1;
    return 0;
}
//求每月多少天
int get_days(int y, int m)
{
    if(m == 2) return 28 + is_leap(y);
    else return months[m];
}
//计算从0000/00/00到现在有多少天
//计算差值就是两个数相减
int cal(int y, int m, int d)
{
    int res = 0;
    for(int i = 1; i < y; i++)
        res += 365 + is_leap(i);
    for(int i = 1; i < m; i++)
        res += get_days(y, i);
    return res + d;
}
//读入
int y, m, d;
scanf("%04d%02d%02d", &y, &m, &d); //yyyymmdd
//读入多组数据
int y1, m1, d1, y2, m2, d2;
while (~scanf("%04d%02d%02d\n%04d%02d%02d", 
			&y1, &m1, &d1, &y2, &m2, &d2))
printf("%d\n", abs(cal(y1, m1, d1) - cal(y2, m2, d2)) + 1);

12. 并查集 O(1)

链接: 合并集合
在O(1)的时间复杂度里合并两个集合,和查询一个点是否在这个集合里。

int p[N]; //存储每个点的祖宗节点

//返回x的祖宗节点
int find(int x)
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
} 
//初始化,假定节点编号1~n
for(int i = 1; i <= n; i++) p[i] = i;

//合并a和b所在的两个集合:
p[find(a)] = find(b);

13. 状态压缩DP

数据范围 >= 30

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值