一)基础算法

浮点数二分算法模板

double find(double l, double r) {
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

高精度加法

// C = A + B, A >= 0, B >= 0
for(int i=a.size()-1; i>=0; i--) A.push_back(a[i] - '0');//输入

vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size(); i ++ )
    {
        t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }
    if (t) C.push_back(t);
    return C;
}

高精度减法

// C = A - B, 满足A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B) {
    if(cmp(A,B) == false) {// A >= B ?
        printf("-");
        return sub(B,A);
    }
    vector<int> C;
    for(int i=0, t=0; i<A.size(); ++i) {
        t = A[i] - t;
        if(i < B.size()) t -= B[i];
        C.push_back((t+10)%10);
        if(t < 0) t = 1;
        else t = 0;
    }
    //去前导0
    while(C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

高精度乘低精度

// C = A * b, A >= 0, b > 0
vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

高精度除以低精度

// A / b = C ... r, A >= 0, b > 0
vector<int> div(vector<int> &A, int b, int &r)
{
    vector<int> C;
    r = 0;
    for (int i = A.size() - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

二维前缀和

S[i, j] = 第i行j列格子左上部分所有元素的和
    s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];//预处理:(1,1)开始
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

一维差分

给区间[l, r]中的每个数加上c:a[l] += c, a[r + 1] -= c

二维差分

给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
a[x1, y1] += c, a[x2 + 1, y1] -= c, a[x1, y2 + 1] -= c, a[x2 + 1, y2 + 1] += c

离散化

vector<int> alls; // 存储所有待离散化的值(查询和修改的)
sort(alls.begin(), alls.end()); 
alls.erase(unique(alls.begin(), alls.end()), alls.end());  
int find(int x) // 找到第一个大于等于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; // 映射到1, 2, ...n
}

二)数据结构

单调栈-O(n)

常见模型:找出每个数左边离它最近的比它大/小的数
    int tt = 0;
    for(int i=1; i<=n; ++i) {//小的
        while(tt && a[stk[tt]] >= a[i]) tt--;
        if(tt) printf("%d ", a[stk[tt]]);
        else printf("-1 ");
        stk[++tt] = i;//下标
    }

单调队列-O(n)

常见模型:找出滑动窗口中的最大值/最小值
    int hh=0, tt=-1;//找最小值
    for(int i=0;i<n; ++i) {
        while(hh<=tt && q[hh]<i-k+1) hh++;
        while(hh<=tt && a[q[tt]]>=a[i]) tt--;
        q[++tt] = i;
        if(i >= k-1) printf("%d ", a[q[hh]]);
    }

KMP-O(n)

寻找字符串p在文本s中出现的位置

// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度, s和p从下标1开始存
for (int i = 2, j = 0; i <= m; i ++ )
{
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}
for (int i = 1, j = 0; i <= n; i ++ )
{
    while (j && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // 匹配成功后的逻辑
        // 匹配位置:i-m+1 (s下标从1开始)
    }
}

Trie树-O(strlen)

询问字符串在字符串集合中出现的次数

int son[N][26], cnt[N], idx;// N为所有字符串的总长度
// cnt[]存储以每个节点结尾的单词数量
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 cnt[N]; // cnt[px] 根才有效
int p[N], d[N]; // d[x]存储x到p[x]的距离
int find(int x) {
    if (p[x] != x)
    {
        int px = p[x];
        p[x] = find(p[x]);//p[x] = root
        d[x] += d[px];
    }
    return p[x];
}
// 合并a和b所在的两个集合:
if(pa != pb) {
    p[pa] = pb;
    d[pa] = 画图确定距离 a->pa b->pb a与b关系 
}

字符串哈希

判断字符串子串是否相等,预处理O(strlen),查询O(1)

typedef unsigned long long ULL;
ULL h[N]; // h[k]存储字符串前k个字母的哈希值
ULL p[N]; // p[k]存储 P^k mod 2^64
int P = 131; p[0] = 1;
// str从1开始存
for (int i = 1; i <= n; i ++ ) {
    h[i] = h[i - 1] * P + str[i];
    p[i] = p[i - 1] * P;
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r) {//画图 h[r]-h[l-1]*(r-l+1)
    return h[r] - h[l - 1] * p[r - l + 1];
}

STL

reverse_iterator: rbegin rend
    
set/multiset
	erase()
            (1) 输入是一个数x,删除所有x  st.erase(x)
            (2) 输入一个迭代器,删除这个迭代器 st.erase(it)
    lower_bound()/upper_bound()
            lower_bound(x)  返回>=x的最小数的迭代器 st.lower_bound(x)
            upper_bound(x)  返回>x的最小数的迭代器 st.upper_bound(x)
    map/multimap
        erase()  输入的参数是pair或者迭代器 
    		mp.erase(it)
    		mp.erase({x,y})
        lower_bound()/upper_bound()
    		lower_bound(x)  返回>=x的最小数的迭代器 mp.lower_bound(x)
            upper_bound(x)  返回>x的最小数的迭代器 mp.upper_bound(x)

bitset, 圧位
    bitset<10000> s;
    ~, &, |, ^
    >>, <<
    ==, !=
    []

    count()  返回有多少个1
    any()  判断是否至少有一个1
    none()  判断是否全为0
    set()  把所有位置成1
    set(k, v)  将第k位变成v
    reset()  把所有位变成0
    flip()  等价于~
    flip(k) 把第k位取反

三)图论

拓扑排序-O(n+m)

bool topsort() {
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ tt] = i;
    while (hh <= tt) {
        int t = q[hh ++ ];
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (-- d[j] == 0)
                q[ ++ tt] = j;
        }
    }
    return tt == n - 1;
}

BF算法-O(km)

边数限制,最多经过 k条边的最短距离

struct Edge  {
    int a, b, w;
}edges[M];
int d[N], backup[N];
// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford() {// 最多k条边到达n
    memset(d,0x3f,sizeof d);
    d[1] = 0;
    for(int i=0; i<k; ++i) {
        memcpy(backup, d, sizeof d);
        for(int i=0; i<m; ++i) {
            int a = edge[i].a, b= edge[i].b, w = edge[i].w;
            d[b] = min(d[b], backup[a]+w);
        }
    }
    if(d[n] > 0x3f3f3f3f/2) return -1;
    return d[n];
}

spfa判断图中是否存在负环 -O(nm)

int dist[N], cnt[N];        // cnt[x]存储1到x的最短路中经过的点数
bool spfa() {
    int hh=0, tt=0;
    for (int i = 1; i <= n; i ++ ) {
        q[tt++] = i;
        st[i] = true;
    }
    while (q.size()) {
        int u = q[hh++];
        if(hh == N) hh = 0;
        st[t] = false;
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;
                if (!st[j]) {
                    st[j] = true;
                    q[tt++] = j;
                    if(tt == N) tt = 0;
                }
    return false;
}

染色法判断二分图-O(n+m)

bool dfs(int u, int c) {
    color[u] = c;
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (color[j] == -1) {
            if (!dfs(j, !c)) return false;
        }
        else if (color[j] == c) return false;
    }
    return true;
}

二分图最大匹配-O(nm)

int n1, n2;  // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx;  // 只会用到从第一个指向第二个的边,存一个方向
int match[N]; // 第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N];
bool find(int x) {
    for (int i = h[x]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!st[j]) {
            st[j] = true;
            if (match[j] == 0 || find(match[j])) {
                match[j] = x;
                return true;
            }
    return false;
}
// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ ) {
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}

差分约束

求最小值->最长路,求最大值->最短路

以最小值为例子:  
i->j
转化成 s[j] >= s[i] + c: add(i,j,c)
    
1.找到定值(原点)
2.确保从原点能走到所有边
3.根据限定条件建图
4.用 *SPFA* 求每个点最长路
dist[i]即为所求的最小值
    
注意:若用SPFA存在环,则表明无解
    
[结合前缀和]
s[0] = 0 原点
a[i]非负-> s[i] >= s[i-1]
其他限制a[i]的条件 or 限制区间和的条件

欧拉路径

1)无向图:

充要条件:除了起点和终点,其余点度数为偶数。度数为奇数的点有0个或2个

从1号点到n号点的欧拉路径 O(mlogm+n)
set<int> g[N];
vector<int> ans;//起点在最后,终点在开头

void dfs(int u) {
    while (g[u].size()) {
        int t = *g[u].begin();
        g[u].erase(t), g[t].erase(u);
        dfs(t);
    }
    ans.push_back(u);
}

2)有向图:

充要条件:要么所有点出度=入度,要么除起点和终点,其余点入度=出度。起点出度比入度多1,终点入度比出度多1

欧拉回路

1)无向图:所有点度数为偶数

2)有向图:所有点入度=出度

缩点tarjan-O(n+m)

for (int i = 1; i <= n; i ++ )
        if (!dfn[i])
            tarjan(i);

void tarjan(int u) {
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u, in_stk[u] = true;
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        }
        else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u]) {
        ++ scc_cnt;
        int y;
        do {
            y = stk[top -- ];
            in_stk[y] = false;
            id[y] = scc_cnt;
            Size[scc_cnt] ++ ;
        } while (y != u);
    }
}

四)数论

试除法分解质因数-O(sqrt(x))

unordered_map<int,int> primes;
void divide(int x) {
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0) {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            primes[i] += s;
        }
    if (x > 1) primes[x] ++;
}

朴素筛质数-O(nloglogn)

void get_primes(int n) {
    for(int i=2; i<=n; ++i) {
        if(!st[i]) {
            prime[++siz] = i;
            for(int j=i; j<=n; j+=i) st[j] = true;
        }

试除法求所有约数(因子)-O(sqrt(x))

vector<int> get_divisors(int x){
    vector<int> res;
    for (int i = 1; i <= x / i; i ++ )
        if (x % i == 0) {
            res.push_back(i);
            if (i != x / i) res.push_back(x / i);
        }
    sort(res.begin(), res.end());
    return res;
}

约数个数和约数之和

如果 N = p1^c1 * p2^c2 * ... *pk^ck // 结合质因数分解
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

快速幂-O(logk)

//求 a^k mod p,时间复杂度 O(logk)。a,b,p无限制关系
int qmi(int a, int k, int p) {//初始a^(2^0) = a
    int res = 1;
    while(k) {
        if(k&1) res = (LL)res * a % p;
        k >>= 1;
        a = (LL)a*a % p;
    }
    return res;
}

①递归法求组合数-O(n^2)

for (int i = 0; i < N; i ++ )//c(2000,2000)
    for (int j = 0; j <= i; j ++ )
        if (!j) c[i][j] = 1;
        else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

②逆元求组合数-O(nlogp)

// C(a,b) = a!/(b!*(a-b)!)
// C(1e5,1e5) mod为质数
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ ) {
    fact[i] = (LL)fact[i - 1] * i % mod;
    infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}

③Lucas求组合数-单次O(plogp)

int lucas(LL a, LL b, int p) {
    if (a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int C(int a, int b, int p) {// 通过定理求组合数C(a, b)
    if (a < b) return 0;
    LL x = 1, y = 1;  // x是分子,y是分母
    for (int i = a, j = 1; j <= b; i --, j ++ ) {//a*(a-1)*...和b!
        x = (LL)x * i % p;
        y = (LL) y * j % p;
    }
    return x * (LL)qmi(y, p - 2, p) % p;
}

卡特兰数

给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为: 
Cat(n) = C(2n, n) / (n + 1)

Nim游戏

NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0

五)数据结构二

树状数组-O(logn)

动态查找区间和,即查找与修改穿插。或者 区间修改,单点查询(差分)

int tr[N], n;//1~n
memset(tr,0,sizeof tr);
void add(int x, int c) {//(位置,要加的值)
    while(x <= n) {
		tr[x] += c;
        x += lowbit(x);
    }
}
int sum(int x) {//前1~x个位置的和
    int res = 0;
    while(x > 0) {
        res += tr[x];
        x -= lowbit(x);
    }
    return res;
}

线段树-O(logn)

struct Node {//1~n
    int l, r;
    LL sum, add;
}tr[N * 4];
int w[N];
void pushup(int u) {// 孩子更新父亲
    tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void eval(Node& u, int add) {
    u.sum += add*(u.r-u.l+1);
    u.add += add;
}
void pushdown(int u) {// 父亲更新孩子懒标记
    eval(tr[u<<1], tr[u].add);
    eval(tr[u<<1|1], tr[u].add);
    tr[u].add = 0;
}
void build(int u, int l, int r) {
    if (l == r) tr[u] = {l, r, w[r], 0};
    else {
        tr[u] = {l, r, 0, 0};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}
void update(int u, int l, int r, int add) {
    if (tr[u].l >= l && tr[u].r <= r) {
        eval(tr[u], add);//打懒标记,并更新区间值
    }
    else {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) update(u << 1, l, r, add);
        if (r > mid) update(u << 1 | 1, l, r, add);
        pushup(u);
    }
}
LL query(int u, int l, int r) {//注意LL
    if (tr[u].l >= l && tr[u].r <= r) {
        return tr[u].sum;  // TODO 需要补充返回值
    } else {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        LL res = 0;
        if (l <= mid ) res = query(u << 1, l, r);
        if (r > mid) res += query(u << 1 | 1, l, r);
        return res;
    }
}

RMQ-O(nlogn)

静态求区间最值:预处理 O(nlogn),查询 O(1)

const int N = 200010, M = 19;
int f[N][M];//表示i开始,长度为2^j的最大值
void init() {
    for(int j=0; j<M; ++j)//长度从小到大枚举
        for(int i=1; i+(1<<j)-1<=n; ++i) {
            if(!j) f[i][j] = a[i];
            else f[i][j] = max(f[i][j-1], f[i+(1<<j-1)][j-1]);//前2^j-1个数,后2^j-1个数
        }
}
int query(int l, int r) {//下表为l~r中返回最大的数
    int len = r - l + 1;
    int k = log2(len);
    return max(f[l][k], f[r-(1<<k)+1][k]);//前2^k个数,后2^k个数
}

六)提高算法

双端队列BFS-O(n+m)

//求边权为01的最短路, 即经过边权为1的最小边数
bool check(int mid) {//类似堆优化版dijkstra
    deque<int> q;
    dist[1] = 0;
    q.push_back(1);
    
    while(q.size()) {
        int u = q.front();
        q.pop_front();   
        if(st[u]) continue;
        st[u] = true;      
        for(int i=h[u]; ~i; i=ne[i]) {
            int j = e[i], d = w[i] > mid;
            if(dist[u] + d < dist[j]) {
                dist[j] = dist[u] + d;
                if(!d) q.push_front(j);
                else q.push_back(j);
            }
    return dist[n] <= k;
}

双向BFS

int bfs() {
    queue<string> qa, qb;
    unordered_map<string,int> da, db;//存到a或b的最短距离
    qa.push(A), qb.push(B);
    da[A] = 0, db[B] = 0;
    while(qa.size() && qb.size()) {
        int t;
        if(qa.size() <= qb.size()) t = extend(qa,da,db);
        else t = extend(qb,db,da);
        if(t <= 10) return t;//不超过10层
    }
    return INF;//还未扩展成功
int extend(queue<string>& q, unordered_map<string,int>& da, unordered_map<string,int>& db,
        string a[], string b[]) {
        for(int k=0,sk=q.size(); k<sk; ++k) {
            auto t = q.front();
            q.pop();           
            //枚举可到达的状态继续搜索            
                 if(da.count(nw)) continue;
                 if(db.count(nw)) return da[t] + db[nw] + 1;
                 da[nw] = da[t] + 1;
                 q.push(nw);
    return INF;
}

A*算法-k短路

启发式搜索,bfs队列换优先队列,取(真实距离+估计距离)小的入队

k短路的启发函数:每个点到终点的最短距离(建反边从终点开始dij)

int astar() {
    priority_queue<PIII,vector<PIII>,greater<PIII>> heap;
    heap.push({dist[S],{0,S}});
    while(heap.size()) {
        auto t  = heap.top();
        heap.pop();
        int u = t.y.y, distance = t.y.x;
        cnt[u]++;
        if(cnt[T] == K) return distance;      
        for(int i = h[u]; ~i; i=ne[i]) {
            int j = e[i];
            if(cnt[j] < K)
                heap.push({distance+w[i]+dist[j], {distance+w[i], j}});
        }
    }
    return -1;
}