Acwing算法基础课笔记

第一讲 简单算法


快速排序

AcWing 785. 快速排序
模板题,给n个数输出排序后的数组

#include<iostream>
#include<cstdio>
using namespace std;

const int N = 100010;
int a[N];

void quick_sort(int a[], int l, int r)
{
    if(l >= r) return ;
    int x = a[l + r >> 1] , i = l - 1 , j = r + 1;
    while(i < j)
    {
        do i ++; while(a[i] < x);
        do j --; while(a[j] > x);
        if(i < j)
            swap(a[i] , a[j]);
    }
    quick_sort(a, l , j); quick_sort(a, j + 1, r);
}

int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)
        scanf("%d",&a[i]);
        
    quick_sort(a, 0, n - 1);
    
    for(int i = 0; i < n; i++)
        printf("%d ",a[i]);
    return 0;
}

AcWing 786. 第k个数
给n个数,问排第k的数是?

这题y总的代码有点巧妙,是快速查找,而没有对整个数组进行排序,当然直接排也可以
我们应该理解一下代码的思路

归并排序

√[AcWing 787. 归并排序]
[AcWing 788. 逆序对的数量]

二分

整数二分

[AcWing 789. 数的范围]

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回 -1 -1。

代码:

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

using namespace std;

int a[100010];

int main()
{
    int n,q;
    cin >> n >> q;
    int al,ar;//al是第一个≥x数的位置,ar是第一个>x数的位置
    for(int i = 0; i < n; i ++ )
        cin >> a[i];
    while( q-- )
    {
        //
        int l = 0, r = n - 1;
        int x;
        cin >> x;
        //二分查al
        while(l < r)
        {
            int mid = l + (r - l) / 2;
            if(a[mid] >= x)
            {
                r = mid;
            }
            else
                l = mid + 1;
        }
        al = l;
        //二分查ar,注意右边界要改成n,因为>x有可能有超出n-1的情况
        l = 0, r = n;
        while(l < r)
        {
            int mid = l + (r - l) / 2;
            if(a[mid] > x)
                r = mid;
            else
                l = mid + 1;
        }
        ar = l;
        if(a[al] == x)
            cout << al << " " << ar - 1 << endl;
        else
            cout << "-1 -1" << endl;
    }
    return 0;
}

我认为算法笔记中的二分部分更加详细好理解
同时,也可以直接使用< algorithm > 中的lower_bound(第一个>=x的数的位置)和upper_bound(第一个>x的数的位置)

写一篇总结
algorithm中的常用函数

浮点数二分

[AcWing 790. 数的三次方根]

给定一个浮点数 n,求它的三次方根。

输入格式
共一行,包含一个浮点数 n。

输出格式
共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 位小数。

数据范围
−10000≤n≤10000

代码

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

using namespace std;

int main()
{
    double n;
    scanf("%lf",&n);
    double l = -100, r = 100;
    while(r - l > 1e-8)
    {
        double mid = (l + r) / 2;
        double x = mid * mid * mid;
        if(x > n) r = mid;
        else l = mid;
    }
    printf("%.6lf\n",l);
    return 0;
}

高精度

[AcWing 791.高精度加法]
[AcWing 792.高精度减法]
[AcWing 793.高精度乘法]
[AcWing 794.高精度除法]

前缀和与差分

AcWing 795. 前缀和

一维前缀模板

#include<iostream>

using namespace std;

int a[100010];

int main()
{
    int n,m;
    cin >> n >> m;
    //输入,注意下标从1开始, a[0] = 0, 为了后面求区间和计算方便
    for(int i = 1; i <= n; i ++ )
        scanf("%d",&a[i]);
    
    //计算前缀和,每个a[i]都是前i个数的和
    for(int i = 2; i <= n; i ++ )
        a[i] += a[i - 1];

    //求一个区间内所有数的和
    for(int i = 0; i < m; i ++ )
    {
        int l,r;
        cin >>l >>r;
        cout << a[r] - a[l - 1] <<endl;
        // a1,a2,a3,a4,a5...,al-1
        // a1,a2,a3,a4,a5...al-1,al,...,ar
    }
    return 0;
}

AcWing 796. 子矩阵的和

二维前缀模板

简单来说就是一个由点( x1,y1)和( x2,y2)构成的矩阵的和的计算公式为:
a[ x2 ][ y2 ] + a[ x1 - 1 ][ y1 - 1 ] - a[ x2 ][ y1 - 1 ] - a[ x1 - 1 ][ y2 ]

在这里插入图片描述

AcWing 797. 差分

一维差分模板,这里理解对差分数列求前缀和就可以获得原数列即可

#include<iostream>

using namespace std;

const int N = 100010;
int a[N],b[N];

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i ++ )
    {
        scanf("%d",&a[i]);
        insert(i,i,a[i]); //完成b数组赋值 b[1] = a[1] 往后每一个b[i]表示a[i] 与 a[i-1] 的差值
    }
    for(int i = 1; i <= m; i ++ )
    {
        int l,r,c;
        scanf("%d%d%d",&l,&r,&c);
        insert(l,r,c);  //⬇下面for求和时会从b[l] 开始到 b[r] 都会 + c 从b[r+1]开始恢复不加c 
    }
    for(int i = 1; i <= n; i ++ )
    {
        b[i] += b[i - 1];
        cout << b[i] << " ";
    }
    return 0;
}

AcWing 798. 差分矩阵

丑是丑了点,凑活看吧。。。
在这里插入图片描述

#include<iostream>

using namespace std;

int a[1005][1005],b[1005][1005];

void insert(int x1,int y1,int x2,int y2,int c)
{
    b[x1][y1] += c;
    b[x2+1][y2+1] += c;
    b[x2+1][y1] -= c;
    b[x1][y2+1] -= c;
}

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];
            insert(i,j,i,j,a[i][j]);
        }
    }
    while(q--)
    {
        int x1,x2,y1,y2,c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1,y1,x2,y2,c);
    }
    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;
    }
    return 0;
}

差分练习题:

Acwing 100. 增减序列

给定一个长度为 n 的数列 a1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。

求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

输入格式
第一行输入正整数 n。

接下来 n 行,每行输入一个整数,第 i+1 行的整数代表 ai。

输出格式
第一行输出最少操作次数。

第二行输出最终能得到多少种结果。

数据范围
0<n≤105,
0≤ai<2147483648
输入样例:
4
1
1
2
2
输出样例:
1
2

显然我们只需要对原数组a求一个差分数组,然后

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

using namespace std;

long long a[100005];

int main()
{
    int n;
    cin >> n; 
    for (int i = 0; i < n; i ++ )
    {
        cin >> a[i];
    }
    long long p = 0, q = 0;
    for (int i = n - 1; i >= 1; i -- )
    {
        a[i] -= a[i-1]; //转换为差分数列
        
        if(a[i] >= 0)
            p += a[i];
        else 
            q += a[i];
    }
    q = -q; //负数转成正数
    cout << max(p,q) << endl << abs(p-q) + 1 ; 
    //最少次数将a1~an的正负先抵消掉 然后剩下的abs(p-q)次,可以决定这个数列的最终结果+0~abs(p-q)种情况
    return 0;
}

双指针算法

AcWing 799. 最长连续不重复子序列

AcWing 800. 数组元素的目标和

给定两个升序排序的有序数组  A  和  B ,以及一个目标值  x 。

数组下标从  0  开始。

请你求出满足  A[i]+B[j]=x  的数对  (i,j) 。

数据保证有唯一解。

输入格式
第一行包含三个整数  n,m,x ,分别表示  A  的长度, B  的长度以及目标值  x 。

第二行包含  n  个整数,表示数组  A 。

第三行包含  m  个整数,表示数组  B 。

输出格式
共一行,包含两个整数  i  和  j 。

数据范围
数组长度不超过  105 。
同一数组内元素各不相同。
1≤数组元素≤109 
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1

代码思路是leetcode101那本书上的,很妙

#include<iostream>

using namespace std;

int a[100005],b[100005];

int main()
{
    int n,m,x;
    cin >> n >> m >> x;
    for(int i = 0; i < n; i ++ )
        cin >> a[i];
    for(int i = 0 ; i < m; i++ )
        cin >> b[i];
    int l = 0,r = m - 1;
    while(a[l] + b[r] != x)
    {
        if(a[l] + b[r] > x && r > 0)
            r --;
        if(a[l] + b[r] < x && l < n)
            l ++ ;
    }
    cout << l << " " << r;
    return 0;
}

[AcWing 2816. 判断子序列]

位运算

√[AcWing 801. 二进制中1的个数]

离散化

[AcWing 802. 区间和]

区间合并

[AcWing 803. 区间合并]


第二讲 数据结构

单链表

AcWing 826. 单链表

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

using namespace std;

const int N = 100010;
int e[N],ne[N];
int idx,head;

//head 表示头结点的下标
//e[i] 表示i结点的值
//ne[i] 表示i结点的next指针
//idx 表示当前节点数+1

//初始化
void init()
{
    head = -1;
    idx = 0;
}

//将x插到头结点
void add_to_head(int x)
{
    e[idx] = x;
    ne[idx] = head;
    head = idx ++ ;
}

//将x插到下标是k点后面
void add(int k, int x)
{
    e[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx ++ ;
}

//将下标是k的点后面的点删掉
void remove(int k)
{
    ne[k] = ne[ne[k]];
}

int main()
{
    int m;
    cin >> m;
    init();
    
    while (m -- )
    {
        int k,x;
        char op;
        cin >> op;
        if(op == 'H')
        {
            cin >> x;
            add_to_head(x);
        }
        else if(op == 'D')
        {
            cin >> k;
            if(k == 0)//如果k == 0 表示删除头结点
                head = ne[head];
            else
                remove(k - 1); //k指插入链表的第k个数,由于idx从0开始,而k从1开始,因此操作要k-1
        }
        else
        {
            cin >> k >> x;
            add(k - 1, x);
        }
    }
    
    for (int i = head ; i != -1 ; i = ne[i]) 
        cout << e[i] << ' ';
    cout << endl;
    return 0;
}

双链表

AcWing 827. 双链表

AcWing 828. 模拟栈
AcWing 3302. 表达式求值

队列

AcWing 829. 模拟队列

单调栈

AcWing 830. 单调栈

单调队列

AcWing 154. 滑动窗口

KMP

AcWing 831. KMP字符串

Trie树(字典树)

AcWing 835. Trie字符串统计
AcWing 143. 最大异或对

模板题:
[AcWing 835. Trie字符串统计]

维护一个字符串集合,支持两种操作:

    I x 向集合中插入一个字符串  x ;
    Q x 询问一个字符串在集合中出现了多少次。

共有  N  个操作,输入的字符串总长度不超过  105 ,字符串仅包含小写英文字母。

输入格式
    第一行包含整数  N ,表示操作数。
    接下来  N  行,每行包含一个操作指令,指令为 I x 或 Q x 中的一种。
输出格式
    对于每个询问指令 Q x,都要输出一个整数作为结果,表示  x  在集合中出现的次数。
    每个结果占一行。

数据范围
1≤N≤2∗10^4 

输入样例:
5
I abc
Q abc
Q ab
I ab
Q ab

输出样例:
1
0
1
#include<iostream>
using namespace std;
const int N = 100010;
int son[N][26], cnt[N], idx;
char str[N];

void insert(char *str)
{
    int p = 0;
    for(int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if(!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }
    cnt[p] ++ ;
}

int query(char *str)
{
    int p = 0;
    for(int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if(!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}

int main()
{
    int n;
    cin >> n;
    while(n --)
    {
        char op;
        cin >> op >> str;
        if(op == 'I') insert(str);
        else cout << query(str) << endl;
    }
    return 0;
}

并查集

AcWing 836. 合并集合
AcWing 837. 连通块中点的数量
AcWing 240. 食物链

AcWing 838. 堆排序
AcWing 839. 模拟堆

哈希表

AcWing 840. 模拟散列表
AcWing 841. 字符串哈希


第三讲 搜索与图论

DFS 与 BFS

DFS

递归与深搜不必太纠结名字和定义

AcWing 842. 排列数字
AcWing 843. n-皇后问题

AcWing 842. 排列数字

视频讲解中的代码,我认为更好理解一些
#include<iostream>

using namespace std;

const int N = 10;
int path[N],st[N];
int n;

void dfs(int u)
{
    //注意这个if判断的条件是dfs(1)开始,如果是dfs(0) 则 (u == n) 时就可以输出了
    if(u == n + 1) // 或者是(u > n) 
    {
        for(int i = 1; i <= n; i ++)
        {
            cout << path[i] << " ";
        }
        cout << endl;
    }
    
    for(int i = 1; i <= n; i ++ ) //每一位都要在1~n中选择数字
    {
        if(!st[i])
        {
            path[u] = i;
            st[i] = 1;
            dfs(u + 1);
            st[i] = 0;
        }
    }
}

int main()
{
    scanf("%d",&n);
    dfs(1);
    return 0;
}
参考代码,更简洁一些 不过就没那么好理解
#include<iostream>

using namespace std;

const int N = 10;
int n;
int path[N];

void dfs(int u, int state)
{
    if(u == n)
    {
        for(int i = 0; i < n; i ++ )
        printf("%d ",path[i]);
        puts("");
    }
    
    for(int i = 0; i < n; i ++ )
    {
        if(!(state >> i & 1)) //判断数字 i+1 有没有被用过
        {
            path[u] = i + 1;
            dfs(u + 1 , state | 1 << i); //继续搜索选择下一位数字 将u位选择的数字i + 1在state中改为1
        }
    }
}

int main()
{
    scanf("%d",&n);
    dfs(0,0);//u从0 开始 第一位 u 是 0
    return 0;
}

题库:递归 第一题
AcWing 92. 递归实现指数型枚举

参考代码:

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

using namespace std;

const int N = 16;

int n;
int st[N];  //状态,记录每个位置当前的状态:0表示还没考虑,1表示选它,2表示不选它

void dfs(int u) //dfs(1)
{
    if(u > n)
    {
        for (int i = 1; i <= n; i ++ )
            if(st[i] == 1)
                printf("%d ",i);
        printf("\n");
        return;
    }
    
    st[u] = 0;
    dfs(u + 1);     //第一个分支:不选
    
    st[u] = 1;
    dfs(u + 1);     
}

void dfs2(int u, int state)//dfs(0,0)
{
    if(u == n) //u == n 时,到叶结点,输出结果
    {
        for(int i = 0 ; i < n ; i ++ )
        {
            if(state >> i & 1)
                cout << i + 1 << " ";
        }
        cout <<endl;
        return;
    }
    dfs2(u + 1, state);
    dfs2(u + 1, state | 1 << u );//注意优先级,左移<< 大于 位或| ,
                                 //因此是先将1 << u位,然后|state,实现对状态的标记
}

int main()
{
    cin >> n;
    
    dfs(1);
    
    return 0;
}

BFS

AcWing 844. 走迷宫
AcWing 845. 八数码

树与图的深度优先遍历

Acwing 846 树的重心

树与图的广度优先遍历

847。 图中点的层次

拓扑排序

Acwing 848.有向图的拓扑排序

最短路

Dijkstra

AcWing 849. Dijkstra求最短路 I
AcWing 850. Dijkstra求最短路 II
849. Dijkstra求最短路 I

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510;

int n,m;
int g[N][N];
int dist[N];
int st[N];

int dijkstra(int s)//s为最短路径的起点,这道模板题是1
{
    //memset(dist, 0x3f, sizeof dist);
    fill(dist, dist + N, 0x3f3f3f3f);//学会使用fill函数,尤其是g数组的二维赋值
    dist[s] = 0;
    
    for(int i = 1; i < n; i ++ )//遍历n - 1次 代表编号 1 ~ n-1 的点
    {
        int t = -1;
        for(int j = 1; j <= n; j ++ )//找当前从起始点能到的 最近的(dis最小) 还未确定(st[]==0)路线的点
        {
            if(!st[j] && (t == -1 || dist[t] > dist[j])) //全局未被确定,且dist最小的j设为下一个确定的点
                t = j;
        }
        
        for(int j = 1; j <= n; j ++ )
        {
            dist[j] = min(dist[j], dist[t] + g[t][j]);//将该点能偶更新的更短路全部更新
        }
        
        st[t] = true;// 标记该点为已确定
    }
    
    if(dist[n] == 0x3f3f3f3f) return -1;//若到n点的距离仍位inf则说明没有到n的的路径
    return dist[n];
}

int main()
{
    scanf("%d%d", &n,&m);
    
    //memset(g, 0x3f, sizeof g);//图的初始化,全部赋为最大值inf = 0x3f3f3f3f
    fill(g[0], g[0] + N * N, 0x3f3f3f3f);
    
    while(m -- )
    {
        int a,b,c;
        cin >> a >> b >> c;
        
        g[a][b] = min(g[a][b],c);//如果果有重边,直接覆盖掉长的重边
    }
    
    printf("%d\n",dijkstra(1));//起点为1
    
    return 0;
}

稍微有点难度的练习题(还需要一个DFS求出最短路径,以及多了一个相同路径长度时,点权的选择)
PTA 天梯练习题L1 - 1 紧急救援
可以看看算法笔记中最短路径部分
AC代码

bellman-ford

AcWing 853. 有边数限制的最短路

spfa

AcWing 851. spfa求最短路
AcWing 852. spfa判断负环

Floyd

AcWing 854. Floyd求最短路

最小生成树

Prim

AcWing 858. Prim算法求最小生成树

Kruskal

AcWing 859. Kruskal算法求最小生成树

二分图

染色法判定二分图

AcWing 860. 染色法判定二分图

匈牙利算法

AcWing 861. 二分图的最大匹配


第四讲 数学知识

质数

质数是对所有>1的数来定义的 : 只包含1和本身两个约数,就叫质数或叫素数
质数的判定方法:
1)试除法

bool is_prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i < n; i ++ ) 
        if(n % i == 0)
            return false;
    return true;
}
时间复杂度O(n)

//优化
bool is_prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i < n / i; i ++ )//不推荐 i <= sqrt(i) 和 i * i <= n sqrt()比较慢,i*i 可能会爆int
        if(n % i == 0)
            return false;
    return true;
}
时间复杂度O(sqrt(n))

//分解质因数


练习题:
AcWing 866. 试除法判定质数
AcWing 867. 分解质因数
AcWing 868. 筛质数

约数

AcWing 869. 试除法求约数
AcWing 870. 约数个数
AcWing 871. 约数之和
AcWing 872. 最大公约数

欧拉函数

AcWing 873. 欧拉函数
AcWing 874. 筛法求欧拉函数

快速幂

AcWing 875. 快速幂
AcWing 876. 快速幂求逆元

扩展欧几里得算法

AcWing 877. 扩展欧几里得算法
AcWing 878. 线性同余方程

中国剩余定理

AcWing 204. 表达整数的奇怪方式

高斯消元

AcWing 883. 高斯消元解线性方程组
AcWing 884. 高斯消元解异或线性方程组

求组合数

AcWing 885. 求组合数 I
AcWing 886. 求组合数 II
AcWing 887. 求组合数 III
AcWing 888. 求组合数 IV
AcWing 889. 满足条件的01序列

容斥原理

AcWing 890. 能被整除的数

博弈论

AcWing 891. Nim游戏
AcWing 892. 台阶-Nim游戏
AcWing 893. 集合-Nim游戏
AcWing 894. 拆分-Nim游戏


第五讲 动态规划

背包问题

01背包

完全背包

多重背包

分组背包

线性DP

区间DP

计数类DP

数位统计DP

状态压缩DP

树形DP

记忆化搜索


第六讲 贪心

区间问题

Huffman树

排序不等式

绝对值不等式

推公式


第七讲 时空复杂度

每一道题都要控制在三分钟之内能够写出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值