算法笔记

算法竞赛备赛

搜索篇
  1. dfs搜索

    深度优先搜索,沿着一条路找到底,其实也是最暴力的方法,采用函数递归的方式进行逐个搜索。适用题目:走地图

    模板代码:

    void dfs(int x, int value){
    	if(x == value){
    		return;
    	}
    	for(int i = 0; i < n; i++){
    		dfs(x+a[i], value);
    	}
    }
    
  2. bfs搜索

    宽度优先搜索,每一步都是再向外扩散,直到所有的格子都被遍历到,或者遍历到结果格子。采用队列的方式进行处理。

    适用题目:时间卡的很紧的走地图、病毒扩散、寻找相连区域

    模板代码:

    //定义结构体
    struct node{
    	int x, y;
        int value;
    };
    while(!q.empty()){
        node tmp = q.front();
        q.pop();
        for(int ; ; ){
            if(条件满足){
                node t;
                t.x = ..; t.y = ..; t.value = tmp.value+/- ..;
                q.push(t);
            }
        }
    }
    
  3. 二分搜索

  4. 暂定

String字符串
  1. String字符串输入(包含空格和不包含空格)

    如果不包含空格,可以直接用cin>>str来进行输入,遇到空格就会停止。

    如果包含空格,可以使用getline(cin, str)来进行输入,但是在输入之前需要使用getchar()来吸收一个换行符。

  2. String字符串的分割

    这里只来说明用单字符进行分割,如空格

    分割思路:用find函数不断寻找到下一个需要分割的位置,再用substr函数进行分割,然后存储到vector中,如果没必要存储也可以直接输出,或者处理使用。

    模板代码:

    // 这里用空格分割
    string str;
    getline(cin, str);
    str = str + " "; //需要在末尾加上一个要分割的字符,否则find函数在最后会因为找不到而返回-1
    int pos = 0;	//记录下一个要分割的位置
    int start = 0;	//记录上一个分割的位置
    vector res;
    while(start<=str.length()-1){
        pos = str.find(" ", start);
        string seq = str.substr(start, pos-start);
        res.push(seq);
        start = pos+1;
    }
    
  3. find函数

    // 1、不指定开始的位置,就默认从0开始寻找
    char c;
    int p = str.find(c);
    // 2、指定开始的位置(下标)pos,则从pos处开始寻找
    char c;
    int pos;
    int p = str.find(c, pos);
    // *注意,find函数返回的是下标,编号从0开始
    
  4. substr函数

    string substr(size_type _Off = 0,size_type _Count = npos)
    // _off 表示起始的位置,下标从0开始
    // _count 表示从起始位置开始截取的字符串数量,如果不加该参数,则直接截取到字符串末尾
    
  5. 暂定

动态规划(dp)篇
  1. 前缀和

    前缀和用来求一段连续的区间的和,在蓝桥杯编程题目中可能会用到,一般都是在输入数据的时候直接进行处理。

    模板代码:

    // 定义前缀和数组dp[1005],其中dp[i]表示前i项的和
    dp[0] = 0;
    for(int i = 1; i <= n; i++){
        cin>>t;
        dp[i] = dp[i-1]+t;
    }
    // 求l - r 区间的和
    int res = dp[r] - dp[l] + a[l];
    
  2. 二维前缀和

    二维前缀和顾名思义就是求一个连续区间的面积,在蓝桥杯题目中这一类题出的会比普通的前缀和要多,求一段连续区间的面积在矩阵中就表示为求一个方阵中所有元素的和。

    // dp[1005][1005],其中dp[i][j]表示从(1,1)~(i,j)区间中元素的和,也就是一个矩阵中元素的和
    memset(dp, 0, sizeof(dp))
    for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                cin>>a[i][j];
                dp[i][j] = dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+a[i][j];
            }
        }
    // 求从(x1, y1)~(x2, y2)区间的和,包含x1,y1部分
    int res = dp[x2][y2]-(dp[x1-1][y2]+dp[x2][y1-1]-dp[x1-1][y1-1])
    
  3. 数位dp

  4. pass

图论篇
  1. 最短路径

    最短路径问题又分为两种,一个是多源最短路问题,一个是单源最短路问题。多源最短路,就是不定源点,最后优化出来的结果任意两个点之间的距离都是最短的;单源最短路需要规定源点,最后求出来是源点到其他点的距离最短。

    主要思路:松弛,如果缩短A–>B的距离,我们这里引入一个其他的点C,使A–>C–>B的距离小于A–>B那么,A点到B点的距离就被缩短了,因此对于单源最短路来说,我们需要找到距离源点最近的点,以此点为中转点对其他所以点进行一遍松弛,当然一遍是不够的,对于n个点就要进行n次松弛,即需要以每个点为中转点进行松弛。

    代码:

    // Dijkstra求单源最短路
    // 定义部分
    int mp[maxn][maxn]; //存储边
    int book[maxn]; //标记数组,标记已经进行松弛的点
    int dist[maxn]; //保存每次松弛的结果
    // 核心代码
    for(int i = 1; i <= n; i++){
            // 每次都去寻找距离源点最近的点
            int dio = inf;
            int u;
            for(int j = 1; j <= n; j++){
                if(!book[j] && dist[j] < dio){
                    dio = dist[j];
                    u = j;
                }
            }
            book[u] = 1;
            for(int v = 1; v <= n; v++){
                if(mp[u][v] < inf)
                    dist[v] = min(dist[u]+mp[u][v], dist[v]);
            }
        }
    // 输出松弛后的结果
        for(int i = 1; i <= n; i++)
                cout<
  2. 并查集

    并查集核心代码在于Union()和Find()函数,Union用于合并两个节点,使他们的祖先相同,Find()函数用来寻找一个数的根节点。模板代码如下:

    // Find函数,来进行寻根
    int Find(int x){
        if(x == root[x]) return root[x];
        else return root[x] = Find(root[x]);	// 路径压缩进行优化
    }
    // Union函数合并两个节点
    void Union(int x, int y){
        int xx = Find(x);
        int yy = Find(y);
        if(xx != yy){
            root[yy] = xx;
        }
    }
    // 所以结点初始化为自己本身
    void init(){
        for(int i = 0; i < Max; i++)
            root[i] = i;
    }
    

    由于在递归的路径压缩中可能会导致数据量很大而爆栈,所以我这里也给出非递归的路径压缩优化。

    int Find(int x){
        int r = x;
        while(r != root[r])
            r = root[r];
        int k = x;
        while(k != r){
            int j = root[k];
            root[k] = r;
            k = j;
        }
        return r;
    }
    
  3. 最小生成树

    最小生成树用来解决连通整个图的最小花费问题。如:在五个城市之间修路的最小花费问题等

    思路:首先按照边排序,边需要用结构体存储,枚举每条边,用并查集检查边的两个端点是否在同一棵树上,不在就合并,在就继续枚举下一个,直到选中的边数等于n-1,n为结点总数。

    知识点:结构体排序,并查集,图的存储,枚举

    代码:

    #include 
    // 图的最小生成树,用来解决走遍所有城市需要的最小开销,或者修所有路的最小开销
    using namespace std;
    typedef long long ll;
    int n, m;
    int sum = 0;
    int root[1001];
    int cnt = 0;
    // 定义结构体和结构体排序方法
    struct node{
        int u, v, w;
    };
    bool cmp(node x, node y){
        return x.w < y.w;
    }
    void init(node e[]){
        for(int i = 0; i <= 1000; i++)
            root[i] = i;
        for(int i = 1; i <= m; i++){
            cin>>e[i].u;
            cin>>e[i].v;
            cin>>e[i].w;
        }
    }
    int Find(int x){
        if(root[x] == x) return x;
        else return root[x] = Find(root[x]);	//路径压缩
    }
    void Union(int x, int y, int w){
        int xx = Find(x);
        int yy = Find(y);
        if(xx == yy) return;
        else{
            sum += w;
            cnt++;
            root[yy] = xx;
        }
    }
    int main(){
        cin>>n>>m;
        node edge[m+1];
        init(edge);
        sort(edge+1, edge+1+m, cmp);
        for(int i = 1; i <= m; i++){
            Union(edge[i].u, edge[i].v, edge[i].w);
            if(cnt == n-1) break;
        }
        cout<
  4. 线段树

  5. pass

数论篇
  1. 容斥原理

  2. 排列组合

  3. 最大公约数(gcd)

    // 求a和b的最大公约数
    #include
    int res = __gcd(a, b);
    
  4. 六倍法求素数

    求一个很大的数是否是素数可以用六倍法来提升寻找效率,原理就是大于等于5的素数都分布在6的倍数的周围。代码如下:

    bool isPrime(int x){
        if(x <= 1)
            return false;
        if(x == 2 || x == 3 || x == 5)
            return true;
        if(x % 2 == 0 || x % 3 == 0)
            return false;
        for(int i = 6; i <= sqrt(x); i+=6){
            if(x % (i-1) == 0 || x % (i+1) == 0)
                return false;
        }
        return true;
    }
    
  5. 欧拉筛求素数集

    上边六倍法适合用来判断一个数是否为素数,这里欧拉筛用来求一个素数组,例如求n以内的所有素数。

    思路就是素数的倍数一定不是素数,以此来筛选所有的非素数

    #define N 100000
    bool check[N]; // 标记非素数
    int prime[N]; // 存储素数集
    void isPrime(int n){
       check[0] = true;
       check[1] = true;
       int cnt = 0;
       for(int i = 2; i <= n; i++){
           if(!check[i]) prime[cnt++] = i;
           for(j = 0; j < cnt&&i*prime[j]<=n; j++){
               check[prime[j]*i] = true;
               if(i % prime[j] == 0) break;
           }
       }
    }
    
小知识点
  1. 乘法逆元

    乘法逆元用来求 (a/b)%mod 问题,众所周知:

    (a+b)%mod = a%mod + b%mod

    (a-b)%mod = a%mod - b%mod

    (a*b)%mod = a%mod * b%mod

    但是 (a/b)mod != a%mod / b%mod ,自己举个例子即可证明。那么我们如果去求(a/b)%mod,这里就需要用的乘法逆元,将除法变为乘法,具体操作是: (a / b)%mod = (a * c) % mod,其中c为b在模mod下的逆元,那么现在的问题是如何求c,也就是在模mod下的b的逆元。求法如下,需要用到快速幂:

    // 快速幂求 x^y
    ll ksm(ll x, ll y){
        ll ans = 1;
        while(y){
            if(y&1){
                ans = ans*x%mod;
            }
            x = x * x % mod;
            y = y >> 1;
        }
        return ans;
    }
    
    // 求 c
    ll c = ksm(b, mod-2);
    
  2. 全排列(next_permutation)

    int a[2,3,4,1,2,3];
    // 注意在使用next_permutation函数之前,需要对目标数组升序排序
    sort(a, a+6);
    do{
    	for(int i = 0; i < 6; i++){
    		cout<
  3. 优先队列(priority_queue)

    // 大根堆
    priority_queue, less >q;
    // 小根堆
    priority_queue, greater >q;
    // 默认为大根堆
    priority_queue q;
    
  4. 二分查找某个数是否存在(binary_search)

    int a[10] = {2,3,1,4,5,1,8,9,3,34};
    int pos = binary_search(a, a+10, 4); //查找4是否出现在整个数组中
    
  5. 去重(set)

    遇见去重复项的就直接用set就对了,也别想啥数组模拟去重,set确实香。

    // 1、string寻找不同字串去重
    set st;
    string str; cin>>str;
    int len = str.length();
    for(int i = 0; i < len; i++){
    	for(int j = i+1; j <= len; j++){
            string s = str.substr(i, j-i);
            st.insert(s);
        }
    }
    // 2、数组去重也可以
    set >st; // 其实和上边string差不多,string的本质也是char数组
    
  6. 高精度加法

    需要使用string去模拟加法,为什么直接用数组,因为输入的时候会直接输入一长串数字,用数组不好存储,所以我这里就直接用string去模拟,更方便和容易理解一点。

    ps:

    1. 小细节就是string不能直接和char相加,如果string需要在末尾添加char,需要使用str.push_back©;

    2. 因为是倒叙相加的,所以最后需要将string字符串进行翻转,需要用到的方法为reverse(str.begin(),str.end()),该方法无返回值。

    // 高精度加法
    string big_add(string a, string b){
        int i = a.length()-1;
        int j = b.length()-1;
        string str = "";	//定义初始返回结果为空
        int t = 0;
        while(i >= 0 && j >= 0){
            int add = (a[i]-'0')+(b[j]-'0')+t;
            char ex = add%10+'0';
            // 因为每一位的结果都是char类型,所以这里就不能直接相加,需要用push_back
            str.push_back(ex);	
            t = add/10;
            i--;
            j--;
        }   
        while(i >= 0){
            int add = (a[i]-'0')+t;
            char ex = add%10+'0';
            str.push_back(ex);
            t = add/10;
            i--;
        }
        while(j >= 0){
            int add = (b[j]-'0')+t;
            char ex = add%10+'0';
            str.push_back(ex);
            t = add/10;
            j--;
        }
        if(t != 0){
            char ex = t+'0';
            str.push_back(ex);
        }
        reverse(str.begin(), str.end());
        return str;
    }
    

你可能感兴趣的:(笔记,蓝桥杯笔记,算法,数据结构)