ACM算法模板小汇总

(文章框架来自学长,内容来源于自我想象,不足之处dd)

入门篇

在入门篇我们会学习很多有用的知识,下面给出一份比较推荐的学习的路径,大家可以根据自己的情况去查缺补漏。

1. 枚举

2. 模拟

3. 递归

4. 前缀和和差分

5. 尺取法

6. 排序算法原理的理解

7. 二分,三分

1> STL自带的二分函数

在库#include <algorithm中有两个函数:upper_boundlower_bound

这两个函数的作用是二分查找一个数在数组中出现的位置。区别是upper返回第一个大于搜索数的位置,而lower是第一个大于等于搜索数的位置。

函数的用法:lower_bound(a.begin(),a.end(),x) – 返回第一个大于等于 x 的数的地址。而由于是地址,在最后要加上−a(也就是减去地址)。

//vector数组中
int index = lower_bound(vec.begin(), vec.end(), t) - vec.begin();
//普通数组中
int index = lower_bound(a + 1, a + n + 1, x) - a;

2> 手写二分

  • 递归形式
int binarySearch(std::vector<int> &nums, int left, int right, int target)
{
    //找不到,返回-1
    if (left > right)
        return -1;

    //防止(left + right)溢出
    int mid = left + (right - left) / 2;

    if (nums[mid] == target)
    {
        //找到target,返回下标
        return mid;
    }
    else if (nums[mid] < target)
    {
        //中间的值小于target,说明target在右边
        return binarySearch(nums, mid + 1, right, target);
    }
    else
    {
        //中间的值大于target,说明target在左边
        return binarySearch(nums, left, mid - 1, target);
    }
}

//重载binarySearch方法,便于调用
int binarySearch(std::vector<int> &nums, int target)
{
    //nums为空则直接返回-1,否则递归查找
    return nums.size() == 0 ? -1 : binarySearch(nums, 0, nums.size() - 1, target);
}
  • 非递归形式
int binarySearch(std::vector<int> &nums, int target)
{
    //nums为空时直接返回-1
    if (nums.size() == 0)
        return -1;

    int left = 0;
    int right = nums.size() - 1;

    while (left <= right)
    {
        //防止(left + right)溢出
        int mid = left + (right - left) / 2;

        if (nums[mid] == target)
        {
            //找到target,返回下标
            return mid;
        }
        else if (nums[mid] < target)
        {
            //中间的值小于target,说明target在右边
            left = mid + 1;
        }
        else
        {
            //中间的值大于target,说明target在左边
            right = mid - 1;
        }
    }
    //找不到,返回-1
    return -1;
}

8. 分治

  • 分治求解三步法:
  1. **划分问题:**把问题的实例划分成子问题。
  2. **递归求解:**递归解决子问题。
  3. **合并问题:**合并子问题的解得到原问题的解。
  • 例:归并排序 (merge_sort) – O(n*logn)
void merge_sort(int *A, int x, int y, int *T){
	if(y - x > 1){
		int m = x + (y - x) / 2; //划分
		int p = x, q = m, i = x;
		merge_sort(A, x, m, T);
		merge_sort(A, m, y, T); //递归求解
		while(p < m || q < y){
			if(q >= y || (p < m && A[p] <= A[q])) T[i++] = A[p++]; //从左半数组复制到临时空间
			else T[i++] = A[q++]; //从右半数组复制到临时空间
		}
		for(i = x; i < y; i++) A[i] = T[i]; //从辅助空间复制回A数组
	}
}

9. 贪心

10. 简单搜索

11. 读入优化

inline int read_int()//快读整数
{
    char c;
    int sign = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-') sign = -1;
    int res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    return res * sign;
}

inline double read_double()//快读实数
{
    char c;
    int sign = 1;
    while((c = getchar()) < '0' || c > '9')
        if(c == '-') sign = -1;
    double res = c - '0';
    while((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    if(c != '.') return res * sign;
    double ll = 0.1;
    while((c = getchar()) >= '0' && c <= '9')
        res += (c - '0') * ll, ll *= 0.1;
    return res * sign;
}
  • stringstream的基本操作
#include <iostream>
#include <cstdio>
#include <sstream>//stringstream的头文件

using namespace std;

int main()
{    
    string line, word;
    while(getline(cin, line))
    {
        stringstream stream(line);
        cout << stream.str() << endl;
        while(stream >> word) { cout << word << endl; }
    }
//    输入:   shanghai no1 school 1989
//    输出:   shanghai no1 school 1989
//            shanghai
//            no1
//            school
//            1989
    printf("\n");
    
    int val1 = 512, val2 = 1024;
    stringstream ss;
    ss << "val1: " << val1 << endl << "val2: " << val2 << endl;
//    "val1: "此处有空格,字符串流是通过空格判断一个字符串的结束
//    将int类型读入ss,变为string类型
    cout << ss.str();
//    输出为:  val1: 512
//             val2: 1024
    printf("\n");
    
    string dump;
    int a, b;
    ss >> dump >> a >> dump >> b;
//    提取512,1024保存为int类型.当然,如果a,b声明为string类型,那么这两个字面值常量相应保存为string类型
    cout << a << " " << b << endl;
//    输出为:  512 1024
    printf("\n");
    
//    stringstream不会主动释放内存(或许是为了提高效率),但如果在程序中用同一个流,反复读写大量的数据,将会造成大量的内存消耗,所以此时,需要用stream.str("")适时地清除一下缓冲
    stringstream sss;
    string s;
    sss << "shanghai no1 school";
    sss >> s;
    cout << "size of stream = " << sss.str().length() << endl;
    cout << "s: " << s << endl;
    sss.str("");
    cout << "size of stream = " << sss.str().length() << endl;
//    输出:   size of stream = 19
//            s: shanghai
//            size of stream = 0
    return 0;
}

something

浮点数

浮点型比较大小时,不能用a == b表示,要用|a - b| < 0.0000001(或其他精确度)。

float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字

double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位

Memset函数

0的二进制是00000000 00000000 00000000 00000000,取后8位00000000,初始化后00000000 00000000 00000000 00000000结果还是0

负数在计算机中以补码存储,-1的二进制是11111111 11111111 11111111 11111111,取后8位11111111,则是11111111 11111111 11111111 11111111结果也是-1

memset(a, 0, sizeof(a)); //初始化为0

memset(a, 255, sizeof(a)); //初始化为-1

不能初始化为1

0x8f的含义:

0x是指16进制,所以16进制的2位可以表示2进制的8位

8则对应1000,f则对应1111;所以每个字节的值就是10001111

众所周知 int 的最高位即32位是表示正负的,第32位为1则为负数,反之为整数

很明显10001111100011111000111110001111这个数是个大负数 (-1886417009)

memset(a, 0x7f, sizeof(a)); //初始化为很大的数(略小于0x7fffffff)

memset(a, 0xaf, sizeof(a)); //初始化为很小的数

memset(a, 0x8f, sizeof(a)); //初始化为很大的负数

基础篇

学会了上面的算法后。我们就有了很好的基础帮我们去理解学习下一步的各种算法。在此我一共把算法大概划分成了4个大类,

  1. 数据结构
  2. 动态规划
  3. 图论
  4. 数学

其中计算几何和博弈论归为数学部分,字符串相关算法归为数据结构。接下来我们对每个部分的基础算法进行了解。

一、基础数据结构

对与基础数据结构,很多都是借助STL实现的,所以希望大家都能熟练掌握STL的用法。

1. 栈

2. 队列

3. 链表

4. 堆

5. 优先队列

6. HASH表

7. 二叉树

8. Trie树

字典树可以看作一棵26叉树。

请看以下题目:

给出n个单词构成的词典,再给出m个字符串。统计有多少个字符串为词典中的单词。

(单词与字符串均为小写字母构成)

以下为用字典树实现的标准程序

#include<bits/stdc++.h>
using namespace std;
int ch[1000000][26];
//ch[i][j]的定义:i号结点的某字母子树的根结点编号。j = int('该字母' - 'a')。当编号为0时,结点不存在。
int main()
{
    int n, m;
    cin >> n >> m;

    //以下为字典树的构造
    int i, j, ls, c, tot = 1, u, sum = 0;
    char s[1000000];
    memset(ch, 0, sizeof(ch));
    for(i = 0; i < n; i++)
    {
        u = 0;
        scanf("%s", s);
        ls = strlen(s);
        for(j = 0; j < ls; j++)
        {
            c = s[j] - 'a';
            if(ch[u][c] == 0)
            {
                tot++;//tot记录结点总数,用于分配编号
                ch[u][c] = tot;
            }
                u = ch[u][c];//将本次修改的结点用于下次操作,相当于在树上深入了一层
        }
    }

    //以下为在字典树上进行的查找操作
    bool b;
    for(i = 0; i < m; i++)
    {
        scanf("%s", s);
        ls = strlen(s);
        b = true;
        u = 0;
        for(j = 0; j < ls; j++)
        {
            c = s[j] - 'a';
            if(ch[u][c] != 0)
            {
                u = ch[u][c];
                continue;
            }
            b = false;
            break;
        }
        for(i = 0; i < 26; i++)//判断字符串是否为完整的单词
        {
            if(ch[u][i] != 0)
            {
                b = false;
                break;
            }
        }
        if(b)
            sum++;
    }

    cout << sum;
    return 0;
}
  • MY
/*
给定n个长度不超过10的数字串,问其中是否存在两个数字串S,T,使得S是T的前缀。有多组数据,数组组数不超过40,n<=10000
*/
/*
思路->在构建过程中:
    若当前串插入后没有新建任何结点,则当前串一定是之前插入的某个串的前缀
    若当前串插入过程中经过某个带有串结尾标记的结点,则之前插入的某个串一定是当前串的前缀
*/
#include<bits/stdc++.h>

using namespace std;

const int maxn = 10010;
int t, n, ans, cnt;
char a[15];
int ch[maxn][15];
bool f[maxn];

inline int read()
{
    char c;
    int f = 1;
    while((c = getchar()) < '0' || c > '9')
        if(c == '-') f = -1;
    int res = c - '0';
    while((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + c - '0';
//        res = res * 10 + c - '0';
    return res * f;
}

bool insert(char *s)
{
    int len = strlen(s);
    int u = 1;
    bool

int main()
{
    t = read();
    for(int i = 1; i <= t; i++)
    {
        cnt = 1;//新的Trie树
        ans = false;
        memset(ch, 0, sizeof(ch));
        memset(f, false, sizeof(f));
        n = read();
        for(int j = 1; j <= n; j++)
        {
            scanf("%s", a);
            if(insert(a)) ans = true;
        }
        if(ans) printf("YES");
        else printf("NO");
    }
    return 0;
}

9. set和map的用法

10. 并查集

并查集合并与查询
n个元素,m次操作
p1 = 1时,将p2与p3所在集合合并
p1 = 2时,输出p2与p3是(Y)否(N)在同一集合内

#include<bits/stdc++.h>

using namespace std;

int k, n, m, s, ans, p1, p2, p3;
int fa[10010];

int find(int k)//路径压缩
{
    if(fa[k] == k) return k;
    return fa[k] = find(fa[k]);
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) fa[i] = i;//初始化
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &p1, &p2, &p3);
        if(p1 == 1) fa[find(p2)] = find(p3);
        else if(find(p2) == find(p3)) printf("Y\n");
        else printf("N\n");
    }
    return 0;
}

11. KMP算法

#include<bits/stdc++.h>

using namespace std;

const int maxn = 1000010;
//i + 1 - m + 1
int n, m;
char a[maxn], b[maxn];
int p[maxn];
int ans[maxn], cnt;

inline int read()
{
    char c;
    int f = 1;
    while((c = getchar()) < '0' || c > '9')
        if(c == '-') f = -1;
    int res = c - '0';
    while((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    return res * f;
}

void pre()
{
    p[1] = 0;
    int j = 0;
    for(int i = 1; i < m; i++)
    {
        while(j && b[i + 1] != b[j + 1]) j = p[j];
        if(b[i + 1] == b[j + 1]) j++;
        p[i + 1] = j;
    }
}

void kmp()
{
    int j = 0;
    for(int i = 0; i < n; i++)//!!!!!!!!从0开始枚举
    {
        while(j && a[i + 1] != b[j + 1]) j = p[j];
        if(a[i + 1] == b[j + 1]) j++;
        if(j == m)
        {
            printf("%d\n", i + 1 - m + 1);
            j = p[j];
        }
    }
}

int main()
{
    scanf("%s", a + 1);
    scanf("%s", b + 1);
    n = strlen(a + 1);
    m = strlen(b + 1);
    pre();
    kmp();
    for(int i = 1; i <= m; i++) printf("%d ", p[i]);//前缀
    printf("\n");
    return 0;
}

12. 单调栈

13. 树状数组

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

int n;
int dp[100001];

inline int read_int()
{
    char c;
    int sign = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-') sign = -1;
    int res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    return res * sign;
}

int lowbit(int x) { return  x & (-x); }//取二进制中最低位的1

void add(int x, int d, int n)//单点加法
{
	while(x <= n)
	{
		dp[x] += d;
		x += lowbit(x);
	}
}

int sum(int q)//区间[1, q]求和
{
	int ans = 0;
	while(q > 0)
	{
		ans += dp[q];
		q -= lowbit(q);
	}
	return ans;
}

int main()
{
	int t, cnt = 0;
	char word[20];
	t = read_int();
	while(t--)
	{
		memset(dp, 0, sizeof(dp));
		n = read_int();
		int m;
		for(int i = 1; i <= n; i++)
		{
			m = read_int();
			add(i, m, n);
		}
		printf("Case %d:\n", ++cnt);
		while(scanf("%s", word) && word[0] != 'E')
        {
            int a = read_int();
            int b = read_int();
            if(word[0] == 'A') add(a, b, n);//a点加b
            else if(word[0] == 'S') add(a, -b, n);//a点减b
            else printf("%d\n", sum(b) - sum(a - 1));//询问区间和[a, b]
		}
	}
	return 0;
}

14. 线段树

//线段树
struct SegmentTree {
    static const int MAXN = 50005; //最大节点数
    //static const int MOD = 1e9 + 7;
    LL a[MAXN];
    LL addLazy[MAXN * 4], mulLazy[MAXN * 4]; //加法lazy标记、乘法lazy标记
    LL sum[MAXN * 4]; //区间和

    void update(int k) {
        //sum[k] = (sum[k << 1] + sum[k << 1 | 1]) % MOD;
        sum[k] = sum[k << 1] + sum[k << 1 | 1];
    }

    //建树
    void build(int k, int l, int r) {
        addLazy[k] = 0;
        mulLazy[k] = 1;
        if(l == r) {
            //sum[k] = a[l] % MOD;
            sum[k] = a[l];
            return ;
        }
        //int mid = (l + ((r - l) >> 1)) % MOD;
        int mid = l + ((r - l) >> 1);
        build(k << 1, l, mid);
        build(k << 1 | 1, mid + 1, r);
        update(k);
    }

    void pushdown(int k, int l, int r) {
        //int mid = (l + ((r - l) >> 1)) % MOD;
        int mid = l + ((r - l) >> 1);

        //维护区间和
        //子节点的值 = 此刻子节点的值 * 父结点的乘法lazy标记 + 父结点的加法lazy标记 * 子节点的区间长度
        //sum[k << 1] = (sum[k << 1] * mulLazy[k] + addLazy[k] * (mid - l + 1)) % MOD;
        sum[k << 1] = sum[k << 1] * mulLazy[k] + addLazy[k] * (mid - l + 1);
        //sum[k << 1 | 1] = (sum[k << 1 | 1] * mulLazy[k] + addLazy[k] * (r - mid)) % MOD;
        sum[k << 1 | 1] = sum[k << 1 | 1] * mulLazy[k] + addLazy[k] * (r - mid);

        //维护lazy标记,修改加法lazy标记时 要遵循乘法优先
        //mulLazy[k << 1] = (mulLazy[k << 1] * mulLazy[k]) % MOD;
        mulLazy[k << 1] = mulLazy[k << 1] * mulLazy[k];
        //mulLazy[k <
  • 2
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
ACM 算法模板集 Contents 一. 常用函数与STL 二. 重要公式与定理 1. Fibonacci Number 2. Lucas Number 3. Catalan Number 4. Stirling Number(Second Kind) 5. Bell Number 6. Stirling's Approximation 7. Sum of Reciprocal Approximation 8. Young Tableau 9. 整数划分 10. 错排公式 11. 三角形内切圆半径公式 12. 三角形外接圆半径公式 13. 圆內接四边形面积公式 14. 基础数论公式 三. 大数模板,字符读入 四. 数论算法 1. Greatest Common Divisor最大公约数 2. Prime素数判断 3. Sieve Prime素数筛法 4. Module Inverse模逆元 5. Extended Euclid扩展欧几里德算法 6. Modular Linear Equation模线性方程(同余方程) 7. Chinese Remainder Theorem中国余数定理(互素于非互素) 8. Euler Function欧拉函数 9. Farey总数 9. Farey序列构造 10. Miller_Rabbin素数测试,Pollard_rho因式分解 五. 图论算法 1. 最小生成树(Kruscal算法) 2. 最小生成树(Prim算法) 3. 单源最短路径(Bellman-ford算法) 4. 单源最短路径(Dijkstra算法) 5. 全源最短路径(Folyd算法) 6. 拓扑排序 7. 网络预流和最大流 8. 网络最小费用最大流 9. 网络最大流(高度标号预流推进) 10. 最大团 11. 二分图最大匹配(匈牙利算法) 12. 带权二分图最优匹配(KM算法) 13. 强连通分量(Kosaraju算法) 14. 强连通分量(Gabow算法) 15. 无向图割边割点和双连通分量 16. 最小树形图O(N^3) 17. 最小树形图O(VE) 六. 几何算法 1. 几何模板 2. 球面上两点最短距离 3. 三点求圆心坐标 4. 三角形几个重要的点 七. 专题讨论 1. 树状数组 2. 字典树 3. 后缀树 4. 线段树 5. 并查集 6. 二叉堆 7. 逆序数(归并排序) 8. 树状DP 9. 欧拉路 10. 八数码 11. 高斯消元法 12. 字符串匹配(KMP算法) 13. 全排列,全组合 14. 二维线段树 15. 稳定婚姻匹配 16. 后缀数组 17. 左偏树 18. 标准RMQ-ST 19. 度限制最小生成树 20. 最优比率生成树(0/1分数规划) 21. 最小花费置换 22. 区间K大数 23. LCA - RMQ-ST 24. LCA – Tarjan 25. 指数型母函数 26. 指数型母函数(大数据) 27. 单词前缀树(字典树+KMP) 28. FFT(大数乘法) 29. 二分图网络最大流最小割 30. 混合图欧拉回路 31. 无源汇上下界网络流 32. 二分图最小点权覆盖 33. 带约束的轨道计数(Burnside引理) 34. 三分法求函数波峰 35. 单词计数,矩阵乘法 36. 字符串和数值hash 37. 滚动队列,前向星表示法 38. 最小点基,最小权点基

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值