个人ACM模板(待持续补充)

前言

该篇文章创立旨意在于保存博主个人常用的一些模板,便于在遗忘时回顾查看,或者需要时方便回顾,思考到放在博客里可以反复查看,也更利于有需要的人学习使用,于是该博客就诞生了。

该博客的模板由于是博主个人归纳和总结的,所以可能不免会出现一些使用上的问题或者程序内的漏洞,如果存在此类漏洞,可以私信博主进行更改。

代码可以任意转载使用,遇到问题也可以私信博主。

1.排序

(1) 快速排序

int arr[N], n;
//arr数组 n元素个数
inline void quicksort(int l, int r)
{
	if (l >= r) return;
	int x = arr[l + r >> 1], i = l - 1, j = r + 1;
	while (i < j)
	{
		do ++i; while (arr[i] < x);
		do --j; while (arr[j] > x);
		if (i < j) swap(arr[i], arr[j]);
	}
	quicksort(l, j), quicksort(j + 1, r);
}

使用: 
-quicksort(0, n - 1) <排序区间[0, n - 1]>
-quicksort(1, n) <排序区间[1, n]>

均为闭区间 对应数组第一个元素和最后一个元素下标


(2) 归并排序(求逆序对)

int arr[N], tmp[N], n;
//arr数组 tmp临时数组 n元素个数
inline void mergesort(int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    mergesort(l, mid), mergesort(mid + 1, r);
    
    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (arr[i] <= arr[j]) tmp[k++] = arr[i++];
        else tmp[k++] = arr[j++], ans = ans + mid - i + 1;//ans是逆序对
        
    while (i <= mid) tmp[k++] = arr[i++];
    while (j <= r) tmp[k++] = arr[j++], ans = ans + mid - i + 1;//ans是逆序对
    
    for (i = l, j = 0; i <= r; ++i, ++j) arr[i] = tmp[j];
}

使用: 
-mergesort(0, n - 1) <排序区间[0, n - 1]>
-mergesort(1, n) <排序区间[1, n]>
-ans <逆序对数>

均为闭区间 对应数组第一个元素和最后一个元素下标



2.基础算法

(1) 二分


bool check(int mid)
{
	return true;
	//return false;
}

void solve()
{
	//板子1
    int l = x, r = y;
    //[l, mid]-> false
    //[mid + 1, r] -> true
    while (l < r)//求区间[x, y]上满足性质的最小值     {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l; //此时l == r

	//板子2
	int l = x, r = y;
	//[l, mid] -> true
	//[mid + 1, r] -> false
    while (l < r)//求区间[x, y]满足性质的最大值
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
	cout << l; //此时l == r
}


使用: 给出区间[x, y]
求出满足性质的 <最小值><板子1>
求出满足性质的 <最大值><板子2>



3.数学

(1) 线性筛(朴素,最小质因子,因子数,欧拉函数)

- 朴素线性筛

const int N = 1e5 + 1;//注意是范围+1

vector<int> prime;
bool isp[N];

void euler_seive() {
    for (int i = 2; i < N; ++i) {
        if (!isp[i]) prime.emplace_back(i);
        for (auto& p : prime) {
            if (i * p >= N) break;
            isp[i * p] = true;
            if (i % p == 0) break;
        }
    }
}


使用:
*euler_seive() <预处理>

-if(!isp[idx]) <对 idx 进行素性判断,质数为false>
-prime[idx]

- 最小质因子筛

const int N = 1e5 + 1;//注意是范围+1

vector<int> prime;
int minp[N];

void euler_seive() {
    for (int i = 2; i < N; ++i) {
        if (!minp[i]) minp[i] = i, prime.emplace_back(i);
        for (auto& p : prime) {
            if (i * p >= N) break;
            minp[i * p] = p;
            if (i % p == 0) break;
        }
    }
}


使用:
*euler_seive() <预处理>

-minp[idx] <数字 idx 的最小质因子>
-prime[idx]

- 因子数筛

const int N = 1e5 + 1;//注意是范围 + 1

vector<int> prime;
int fac[N], cnt[N];//cnt为最小质因子数 fac为因子数

void euler_seive() {
    fac[1] = 1;//特判 1
    for (int i = 2; i < N; ++i) {
        if (!fac[i]) fac[i] = 2, cnt[i] = 1, prime.emplace_back(i);
        for (auto& p : prime) {
            if (p * i >= N) break;
            if (i % p == 0) {
                fac[i * p] = fac[i] / (cnt[i] + 1) * (cnt[i] + 2);
                cnt[i * p] = cnt[i] + 1;
                break;
            }
            fac[i * p] = fac[i] * fac[p];
            cnt[i * p] = 1;
        }
    }
}


使用:
*euler_seive() <预处理>

-fac[idx] <数字 idx 的因子数>
-cnt[idx] <数字 idx 的最小质因子数>
-primes[idx]


- 欧拉函数筛

const int N = 1e5 + 1;// 注意是范围 + 1

vector<int> prime;
int phi[N];

void euler_seive() {
    phi[1] = 1;//特判 1
    for (int i = 2; i < N; ++i) {
        if (!phi[i]) phi[i] = i - 1, prime.emplace_back(i);
        for (auto& p : prime) {
            if (i * p >= N) break;
            if (i % p == 0) {
                phi[i * p] = phi[i] * p;
                break;
            }
            phi[i * p] = phi[i] * (p - 1);
        }
    }
}


使用:
*euler_seive() <预处理>

-phi[idx] <数字 idx 的欧拉函数值>
-primes[idx]


(2) 快速幂 (龟速乘)


using i64 = long long;

const int mod = 1e9 + 7;

// a 为底数 b 为指数 mod 为模数
i64 qpow(i64 a, i64 b) {
    i64 res = 1; a %= mod;
    for (; b; b >>= 1, a = a * a % mod) {
        if (b & 1) res = res * a % mod;
    }
    return res;
}

//龟速乘
// a, b 为两个因数 mod 为模数
i64 smul(i64 a, i64 b) {
    i64 res = 0;
    for (; b; b >>= 1, a = (a + a) % mod) {
        if (b & 1) res = (res + a) % mod;
    }
    return res;
}


使用:
-qpow(a, b) < a 的 b 次方>
-smul(a, b) < a 乘 b >

(3) 欧几里得算法 (gcd, exgcd)

using i64 = long long;

//朴素gcd
i64 gcd(i64 a, i64 b)
{
	return b ? gcd(b, a % b) : a;
}

//exgcd
i64 x, i64 y;
i64 exgcd(i64 a, i64 b, i64& x, i64& y)
{
	i64 res;
	if (!b) { x = 1, y = 0; return a; }
	else res = exgcd(b, a % b, y, x), y -= a / b * x;
	return res;
}

使用:
朴素gcd: 
-gcd(a, b)

exgcd求逆元:
-exgcd(a, mod, x, y), x = (x % mod + mod) % mod


(4) jiangly的模板元板子

constexpr int P = 998244353;
using i64 = long long;
// assume -P <= x < 2P
int norm(int x) {
    if (x < 0) {
        x += P;
    }
    if (x >= P) {
        x -= P;
    }
    return x;
}
template<class T>
T power(T a, i64 b) {
    T res = 1;
    for (; b; b /= 2, a *= a) {
        if (b % 2) {
            res *= a;
        }
    }
    return res;
}
struct Z {
    int x;
    Z(int x = 0) : x(norm(x)) {}
    Z(i64 x) : x(norm(x % P)) {}
    int val() const {
        return x;
    }
    Z operator-() const {
        return Z(norm(P - x));
    }
    Z inv() const {
        assert(x != 0);
        return power(*this, P - 2);
    }
    Z &operator*=(const Z &rhs) {
        x = i64(x) * rhs.x % P;
        return *this;
    }
    Z &operator+=(const Z &rhs) {
        x = norm(x + rhs.x);
        return *this;
    }
    Z &operator-=(const Z &rhs) {
        x = norm(x - rhs.x);
        return *this;
    }
    Z &operator/=(const Z &rhs) {
        return *this *= rhs.inv();
    }
    friend Z operator*(const Z &lhs, const Z &rhs) {
        Z res = lhs;
        res *= rhs;
        return res;
    }
    friend Z operator+(const Z &lhs, const Z &rhs) {
        Z res = lhs;
        res += rhs;
        return res;
    }
    friend Z operator-(const Z &lhs, const Z &rhs) {
        Z res = lhs;
        res -= rhs;
        return res;
    }
    friend Z operator/(const Z &lhs, const Z &rhs) {
        Z res = lhs;
        res /= rhs;
        return res;
    }
    friend std::istream &operator>>(std::istream &is, Z &a) {
        i64 v;
        is >> v;
        a = Z(v);
        return is;
    }
    friend std::ostream &operator<<(std::ostream &os, const Z &a) {
        return os << a.val();
    }
};



(5) jiangly的组合数板子


using i64 = long long;
template<class T>
constexpr T power(T a, i64 b) {
    T res = 1;
    for (; b; b /= 2, a *= a) {
        if (b % 2) {
            res *= a;
        }
    }
    return res;
}
 
template<int P>
struct MInt {
    int x;
    constexpr MInt() : x{} {}
    constexpr MInt(i64 x) : x{norm(x % P)} {}
    
    constexpr int norm(int x) const {
        if (x < 0) {
            x += P;
        }
        if (x >= P) {
            x -= P;
        }
        return x;
    }
    constexpr int val() const {
        return x;
    }
    explicit constexpr operator int() const {
        return x;
    }
    constexpr MInt operator-() const {
        MInt res;
        res.x = norm(P - x);
        return res;
    }
    constexpr MInt inv() const {
        assert(x != 0);
        return power(*this, P - 2);
    }
    constexpr MInt &operator*=(MInt rhs) {
        x = 1LL * x * rhs.x % P;
        return *this;
    }
    constexpr MInt &operator+=(MInt rhs) {
        x = norm(x + rhs.x);
        return *this;
    }
    constexpr MInt &operator-=(MInt rhs) {
        x = norm(x - rhs.x);
        return *this;
    }
    constexpr MInt &operator/=(MInt rhs) {
        return *this *= rhs.inv();
    }
    friend constexpr MInt operator*(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res *= rhs;
        return res;
    }
    friend constexpr MInt operator+(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res += rhs;
        return res;
    }
    friend constexpr MInt operator-(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res -= rhs;
        return res;
    }
    friend constexpr MInt operator/(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res /= rhs;
        return res;
    }
    friend constexpr std::istream &operator>>(std::istream &is, MInt &a) {
        i64 v;
        is >> v;
        a = MInt(v);
        return is;
    }
    friend constexpr std::ostream &operator<<(std::ostream &os, const MInt &a) {
        return os << a.val();
    }
    friend constexpr bool operator==(MInt lhs, MInt rhs) {
        return lhs.val() == rhs.val();
    }
    friend constexpr bool operator!=(MInt lhs, MInt rhs) {
        return lhs.val() != rhs.val();
    }
};
 
template<int V, int P>
constexpr MInt<P> CInv = MInt<P>(V).inv();
 
constexpr int P = 998244353;
using Z = MInt<P>;
 
struct Comb {
    int n;
    std::vector<Z> _fac;
    std::vector<Z> _invfac;
    std::vector<Z> _inv;
    
    Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
    Comb(int n) : Comb() {
        init(n);
    }
    
    void init(int m) {
        if (m <= n) return;
        _fac.resize(m + 1);
        _invfac.resize(m + 1);
        _inv.resize(m + 1);
        
        for (int i = n + 1; i <= m; i++) {
            _fac[i] = _fac[i - 1] * i;
        }
        _invfac[m] = _fac[m].inv();
        for (int i = m; i > n; i--) {
            _invfac[i - 1] = _invfac[i] * i;
            _inv[i] = _invfac[i] * _fac[i - 1];
        }
        n = m;
    }
    
    Z fac(int m) {
        if (m > n) init(2 * m);
        return _fac[m];
    }
    Z invfac(int m) {
        if (m > n) init(2 * m);
        return _invfac[m];
    }
    Z inv(int m) {
        if (m > n) init(2 * m);
        return _inv[m];
    }
    Z binom(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac(n) * invfac(m) * invfac(n - m);
    }
} comb;


(6) ygg的组合数板子


using i64 = long long;
const int mod = 1e9 + 7;
struct Comb {
    const int N;
    vector<i64> fac, invfac;
    Comb(int n) : N(n), fac(n + 2), invfac(n + 2) { init(); };
    i64 qpow(i64 x, i64 p) {
        i64 res = 1 % mod; x %= mod;
        for (; p; p >>= 1, x = x * x % mod)
            if (p & 1) res = res * x % mod;
        return res;
    }
    i64 inv(i64 x) { return qpow(x, mod - 2); };
    void init() {
        fac[0] = 1;
        for (int i = 1; i <= N; ++i) fac[i] = fac[i - 1] * i % mod;
        invfac[N] = inv(fac[N]);
        for (int i = N - 1; i >= 0; --i) invfac[i] = (invfac[i + 1] * (i + 1)) % mod;
    }
    i64 C(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
    }
    i64 A(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac[n] * invfac[n - m] % mod;
    }
};



4.数据结构

(1) 单调队列 (单调递减,递增)

void solve()
{
	int n, k;//n为数组长度 k为区间长度
	vector<int> arr(n), q(n);//arr为元素 q为单调队列
	
	int hh = 0, tt = -1;//hh为队头指针 tt为队尾指针
	for (int i = 0; i < n; ++i)
	{
		while (hh <= tt && q[hh] + k <= i) ++hh;//判断过期
		while (hh <= tt && arr[i] <= arr[q[tt]]) --tt;//对递减:弹出不满足元素
		//while (hh <= tt && arr[i] >= arr[q[tt]]) --tt;//对递增:弹出不满足元素
		q[++tt] = i;//更新队尾 存下标
		if (i >= k - 1) //操作
	}
}

使用:solve() 函数所示


(2) 树状数组(前缀和,差分)

using i64 = long long;
int tree[N], n;//tree存树 n为数组下标

i64 Query(int x)//查询闭区间 [1,x] 的前缀和
{
	i64 res = 0;
	for (int i = x; i > 0; i -= i & -i) 
		res += tree[i];
	return res;
}

void Modify(int l, int z)//向闭区间 [l,n] 加上数值 z
{
	for (int i = l; i <= n; i += i & -i)
		tree[i] += z;
}

void solve()
{
    //构造前缀和树状数组
    for (int i = 1, z; i <= n; ++i)
    {
        cin >> z;
        upd(i, z);
    }
    //构造差分树状数组
    for (int i = 1, num, last = 0; i <= n; ++i)
    {
        cin >> num;
        upd(i, num - last);
        last = num;
    }
    //用已有数组构造
    for (int i = 1, num, last = 0; i <= n; ++i)
    {
    	upd(i, arr[i]);//前缀和
    	upd(i, arr[i] - arr[i - 1])//差分
    }
}

使用: 
前缀和树状数组:
-upd(i, z) <使区间 [i,n] 加上 z>
-qry(y) - qry(x - 1) <查询[x,y]的区间和>

差分树状数组:
-upd(x, z), upd(y + 1, -z) <同时使用,使区间 [x,y] 每个数都加上 z>
-qry(x) <查询数组下标为 x 的值>



(3) 线段树(维护区间和模板)

using i64 = long long;

#define lson k << 1, l, mid//宏定义减少代码量
#define rson k << 1 | 1, mid + 1, r
i64 seg[N << 2], lazy[N << 2];//四倍空间

void pushdown(int k, int l, int r)
{
	//左儿子有mid - l + 1个元素 右儿子有 r - mid个元素
	//lt为左儿子下标 rt为右儿子下标
	int mid = l + r >> 1, lt = k << 1, rt = k << 1 | 1;
	seg[lt] += lazy[k] * (mid - l + 1), seg[rt] += lazy[k] * (r - mid);//更新seg
	lazy[lt] += lazy[k], lazy[rt] += lazy[k], lazy[k] = 0;//更新懒标记
}

void build(int k, int l, int r)//构建线段树
{
	lazy[k] = 0;
	if (l == r) return void(cin >> seg[k]);//直接构建
	//if (l == r) return void(seg[k] = arr[l]);用已有数组构建
	//if (l == r) return void (seg[k] = arr[id[l]]);重链剖分建树
	int mid = l + r >> 1;
	build(lson), build(rson);
	seg[k] = seg[k << 1] + seg[k << 1 | 1];
}

void upd(int k, int l, int r, int x, int y, i64 z)//用 z 更新闭区间[x, y]
{
	if (l >= x && r <= y)
	{
		lazy[k] = lazy[k] + z;
		seg[k] += z * (r - l + 1);
		return;
	}
	int mid = l + r >> 1;
	if (lazy[k]) pushdown(k, l, r);
	if (x <= mid) upd(lson, x, y, z);
	if (y > mid) upd(rson, x, y, z);
	seg[k] = seg[k << 1] + seg[k << 1 | 1];
}

i64 qry(int k, int l, int r, int x, int y)//查询闭区间[x, y]
{
	if (l >= x && r <= y) return seg[k];
	if (lazy[k]) pushdown(k, l, r);
	int mid = l + r >> 1;
	i64 res = 0;
	if (x <= mid) res += qry(lson, x, y);
	if (y > mid) res += qry(rson, x, y);
	return res;
}

使用: 
*build(1, 1, n) (建树)

-upd(1, 1, n, x, y, z) (如上)
-qry(1, 1, n, x, y) (如上)

前三个1, 1, n为固定输入 n为数组最后一个元素下标 数组区间严格为[1, n]
后三个x y z分别代表区间 [x,y] 和 增添修改的值(查询时不用z)


(4) 重链剖分(维护树结构)

int siz[N], dep[N], son[N], fa[N];//dfs1相关
//siz 以该节点为树的大小 dep节点的深度 son节点的重儿子 fa节点的父亲
int dfn[N], id[N], top[N], cnt;//dfs2相关
//dfn 剖分序 id建树回溯原下标 top节点的重链头 cnt维护dfn序

inline void dfs1(int u, int ufa)//跑dfs1相关 预处理dfs2
{
	siz[u] = 1, dep[u] = dep[ufa] + 1, fa[u] = ufa;
	for (auto& x : g[u])
	{
		if (x == ufa) continue;
		dfs1(x, u), siz[u] += siz[x];
		if (siz[x] > siz[son[u]]) son[u] = x;
	}
}

inline void dfs2(int u, int tp)//跑dfn序
{
	top[u] = tp, id[++cnt] = u, dfn[u] = cnt;
	if (son[u]) dfs2(son[u], tp);
	for (auto& x : g[u])
		if (x == fa[u] || x == son[u]) continue;
		else dfs2(x, x);
}

void updlink(int x, int y, i64 z)//对节点x->y的最短链上每个节点都加上z
{
	while (top[x] != top[y])
	{
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		upd(1, 1, n, dfn[top[x]], dfn[x], z);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	upd(1, 1, n, dfn[x], dfn[y], z);
}

i64 qrylink(int x, int y)//询问节点x->y的最短链上每个节点的和
{
	i64 res = 0;
	while (top[x] != top[y])
	{
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		res += qry(1, 1, n, dfn[top[x]], dfn[x]);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	res += qry(1, 1, n, dfn[x], dfn[y])
	return res;
}

使用: 给出树上两点(x, y) 和 根(root)
*build(1, 1, n) <预处理线段树 建树如线段树所示>
*dfs1(root, 0), dfs2(root, root) <剖分 root为根>

-updlink(x, y, z) <给 x 到 y 最短链上的所有节点加上 z>
-qrylink(x, y) <查询从 x 到 y 最短链上所有节点的和>
-upd(1, 1, n, dfn[x], dfn[x] + siz[x] - 1, z) <给以节点x为根的子树都加上z>
-qry(1, 1, n, dfn[x], dfn[x] + siz[x] - 1) <查询以节点x为根的子树上所有节点的和>

(5) 分块(维护区间和模板)


using i64 = long long;
i64 arr[N], n;//arr是原数组 n是数组长度
// len 块长 tot 块数;
i64 len, tot;
// sum 单块的和
// lt,rt 这个块的左右下标
// pos 数组下标 [i] 所在的块编号
// lazy 懒标记
i64 sum[M], lt[M], rt[M], lazy[M], pos[N];

void build()
{
    len = (i64)sqrt(n), tot = (n - 1) / len + 1;
    // 处理区间left block, right block下标
    for (int i = 1; i <= tot; ++i) lt[i] = rt[i - 1] + 1, rt[i] = i * len;
    rt[tot] = n;// 末端特判
    for (int i = 1; i <= tot; ++i)//初始化tot个块
        for (int j = lt[i]; j <= rt[i]; ++j)
            pos[j] = i, sum[i] += arr[j];
}

void upd(int x, int y, i64 z)// 对区间[x, y] 加上值z
{
    int lb = pos[x], rb = pos[y];//lb rb 代表左右两块的编号
    if (lb == rb)// 区间[x, y]在同块内时
    {
        for (int i = x; i <= y; ++i) arr[i] += z, sum[lb] += z;
        return;
    }
    for (int i = lb + 1; i <= rb - 1; ++i) sum[i] += len * z, lazy[i] += z;//更新[x, y]中间的整块 和 懒标记
    for (int i = x; i <= rt[lb]; ++i) arr[i] += z, sum[lb] += z;//补上小段[x , rt[lb]]
    for (int i = y; i >= lt[rb]; --i) arr[i] += z, sum[rb] += z;//补上小段[lt[rb] , y]
}

i64 qry(int x, int y)//查询 [x, y] 区间的和
{
    int lb = pos[x], rb = pos[y]; i64 res = 0;
    if (lb == rb)// 区间[x, y]在同块内时
    {
        for (int i = x; i <= y; ++i) res += arr[i] + lazy[lb];
        return res;
    }
    for (int i = lb + 1; i <= rb - 1; ++i) res += sum[i];// 区间[x, y]中间的整块
    for (int i = x; i <= rt[lb]; ++i) res += arr[i] + lazy[lb];//补上小段[x , rt[lb]]
    for (int i = y; i >= lt[rb]; --i) res += arr[i] + lazy[rb];//补上小段[lt[rb] , y]
    return res;
}



使用: 给出区间[x, y]
*build() <预处理分块>

-upd(x, y, z) <[x, y]上的数字都加上z>
-qry(x, y) <查询[x, y]的区间和>

(6) 并查集


int fa[N], siz[N];//fa是父亲 siz是所在并查集大小

int find(int x)//查询父节点
{
    while (x != fa[x]) x = fa[x] = fa[fa[x]];
    return x;
}
//查询 x 所在并查集大小
int size(int x) { return siz[find(x)]; }
//查询是否在同一区间 
bool same(int x, int y) { return find(x) == find(y); }

bool merge(int x, int y)//合并 x 和 y 所在并查集
{
    x = find(x), y = find(y);
    if (x == y) return false;
    siz[x] += siz[y];
    fa[y] = x;
    return true;
}

使用: 给出两点 (x, y)
*for(int i = 1; i <= n; ++i) siz[i] = 1, fa[i] = i; <预处理并查集>

-find(x) <查询 x 节点的父亲>
-size(x) <查询 x 节点所在并查集大小>
-same(x, y) <查询 x 节点和 y 节点是否在同一并查集内>
-merge(x, y) <合并 x 节点和 y 节点的并查集>


(7) 可持久化线段树(维护区间和)


using i64 = long long;

const int N = 4e6 + 5, M = 1e5 + 5;

//seg是树的值 ls是左儿子 rs是右儿子 lazy是懒标记
//lson rson 用来优化代码量
#define seg(x) tree[x].val
#define ls(x) tree[x].l
#define rs(x) tree[x].r
#define lson(x) tree[x].l, l, mid
#define rson(x) tree[x].r, mid + 1, r
#define lazy(x) tree[x].lazy

//ver代表着每一次修改的根节点下标 idx辅助开点
int ver[M], idx, n, m;

struct Segtree {
    i64 val;
    int l, r, lazy;
}tree[N];

void build(int k, int l, int r) {
    seg(k) = lazy(k) = 0;//多测清空
    if (l == r) return void(cin >> seg(k));//构建方式可参考线段树
    int mid = l + r >> 1;
    //动态开点确保空间复杂度
    ls(k) = ++idx, rs(k) = ++idx;
    build(lson(k)), build(rson(k));
    seg(k) = seg(ls(k)) + seg(rs(k));
}

//pre代表着历史版本
void upd(int pre, int k, int l, int r, int x, int y, int z) {
    tree[k] = tree[pre];//先让其等于历史值
    if (l >= x && r <= y) {
        lazy(k) += z;
        seg(k) += z * (r - l + 1);
        return;
    }
    int mid = l + r >> 1;
    //在修改的同时动态开点
    if (x <= mid) ls(k) = ++idx, upd(ls(pre), lson(k), x, y, z);
    if (y > mid) rs(k) = ++idx, upd(rs(pre), rson(k), x, y, z);
    seg(k) = seg(ls(k)) + seg(rs(k)) + lazy(k) * (r - l + 1);//注意同时加上懒标记
}

//因为空间大小原因不可下放标记 利用永久化标记 mark 辅助查询
i64 qry(int k, int l, int r, int x, int y, i64 mark) {
    if (l >= x && r <= y) return seg(k) + (r - l + 1) * mark;
    int mid = l + r >> 1;
    i64 res = 0;
    if (x <= mid) res += qry(lson(k), x, y, mark + lazy(k));
    if (y > mid) res += qry(rson(k), x, y, mark + lazy(k));
    return res;
}

void solve() {

    cin >> n >> m;

    idx = 0;
    ver[0] = ++idx;
    build(ver[0], 1, n);

    char op; int x, y, z, now = 0;
    for (int i = 1; i <= m; ++i) {
        cin >> op;
        if (op == 'C') {
            cin >> x >> y >> z;
            ver[++now] = ++idx;
            upd(ver[now - 1], ver[now], 1, n, x, y, z);
        }
        else if (op == 'Q') {
            cin >> x >> y;
            cout << qry(ver[now], 1, n, x, y, 0) << '\n';
        }
        else if (op == 'H') {
            cin >> x >> y >> z;
            cout << qry(ver[z], 1, n, x, y, 0) << '\n';
        }
        else if (op == 'B'){
            cin >> now;
        }
    }
}


使用:给出一个区间 <[x, y]><历史版本z><修改值z>

<预处理线段树,版本从 0 开始>
*idx = 0, ver[0] = ++idx,build(ver[now], 1, n) 

<使当前版本的区间 [x, y] 同时加上一个值 z ,并更新当前版本> (Change)
-ver[++now] = ++idx <更新版本>
-upd(ver[now - 1], ver[now], 1, n, x, y, z); 

<查询当前版本区间 [x, y] 的数字的和> (Query)
-qry(ver[now], 1, n, x, y, 0)

<查询第 z 次修改的版本区间 [x, y] 的数字的和> (History)
-qry(ver[z], 1, n, x, y, 0)

<将当前版本回溯至第 z 次修改的版本> (Back)
-now = z

以上四种操作分别对应 solve(): op = C, Q, H, B。
单次修改需要的空间开销很大,请不要吝啬空间,能开多大则开多大。
<除动态开点及宏定义外,其他内容与线段树结构相似>


(8) 珂朵莉树(ODT)


using i64 = long long;

struct Node {
    i64 l, r;
    mutable i64 v;
    Node(i64 l, i64 r, i64 v) : l(l), r(r), v(v) {}
    bool operator<(const Node& t) const { return l < t.l; };
};

set<Node> ODT;
//拆分区间操作 <ODT核心>
set<Node>::iterator split(i64 pos) {
    auto it = ODT.lower_bound(Node(pos, 0, 0));
    if (it != ODT.end() && it->l == pos) return it;
    i64 l = (--it)->l, r = it->r, v = it->v;
    ODT.erase(it);
    ODT.emplace(l, pos - 1, v);
    return ODT.emplace(pos, r, v).first;
}
//区间赋值操作
void assign(i64 l, i64 r, i64 v) {
    auto end = split(r + 1), begin = split(l);
    ODT.erase(begin, end);
    ODT.emplace(l, r, v);
}
//区间加
void add(i64 l, i64 r, i64 v) {
    auto end = split(r + 1);
    for (auto it = split(l); it != end; ++it)
        it->v += v;
}
//快速幂模板
i64 qpow(i64 a, i64 b, i64 Mod) {
    i64 res = 1 % Mod; a %= Mod;
    for (; b; b >>= 1, a = a * a % Mod)
        if (b & 1) res = res * a % Mod;
    return res;
}
//区间平方和
i64 rangepow(i64 l, i64 r, i64 x, i64 y) {
    i64 res = 0;
    auto end = split(r + 1);
    for (auto it = split(l); it != end; ++it)
        res = (res + qpow(it->v, x, y) * (it->r - it->l + 1)) % y;
    return res;
}
//区间第k小数
i64 kth(i64 l, i64 r, i64 k) {
    auto end = split(r + 1);
    vector<pair<i64, i64>> rnk;
    for (auto it = split(l); it != end; ++it)
        rnk.emplace_back(it->v, it->r - it->l + 1);
    sort(rnk.begin(), rnk.end());
    int rnksz = rnk.size();
    for (int i = 0; i < rnksz; ++i) {
        k -= rnk[i].second;
        if (k <= 0) return rnk[i].first;
    }
    return -1;
}


使用: 给出数组 <arr>,区间 <[l, r]><数字x><模数y>

*利用for循环插入数组中的每一个点 <预处理>
	for (int i = 1; i <= n; ++i) {
	    ODT.emplace(i, i, arr[i]);
	}

-assign(l, r, x) <将闭区间 [l, r] 内所有数字改为 x>
-add(l, r, x) <将闭区间 [l, r] 内所有数字加上 x>
-kth(l, r, x) <求出闭区间 [l, r] 内第 x 小的数字>
-rangepow(l, r, x, y) <求出闭区间 [l, r] 内所有数字在模 y 下的 x 次方之和>


(9) 莫队模板


i64 tot;//每一块的长度
struct Ask {
    int l, r, id;
    bool operator<(const Ask& t) const {
        if (l / tot != t.l / tot) return l < t.l;
        if (l / tot & 1) return r > t.r;
        return r < t.r;
    }
};

void solve() {

    int n, q;
    cin >> n >> q;

    tot = n / sqrtl(n);//算出块长

    vector<int> ans(q);//答案
    vector<Ask> ask(q);//离线
    sort(ask.begin(), ask.end());

    int cur = 0;//当前的答案贡献
    auto add = [&](int pos) {
        //加入的贡献
    };

    auto del = [&](int pos) {
        //删除的贡献
    };

    int i = 1, j = 0;
    for (int now = 0; now < q; ++now) {
        int l = ask[now].l, r = ask[now].r, id = ask[now].id;
        while (i > l) add(--i);
        while (j < r) add(++j);
        while (i < l) del(i++);
        while (j > r) del(j--);
        ans[id] = cur;//等于答案贡献
    }
    //多测清空
    while (j < n) add(++j);
    while (i <= n) del(i++);

}



(10) 主席树模板(静态查询区间第 k 小)


const int N = 2e5 + 10;//数组长度

int idx;
struct Segtree {
    int cnt, l, r;
}tree[N << 5];//32倍空间

//宏定义减少代码量
#define seg(k) tree[k].cnt
#define ls(k) tree[k].l
#define rs(k) tree[k].r
#define lson(k) tree[k].l, l, mid
#define rson(k) tree[k].r, mid + 1, r

//创建新节点
int creat(int pre) {
    tree[++idx] = tree[pre];
    return idx;
}

//更新版本
int upd(int& pre, int l, int r, int x, int y) {
    int cur = creat(pre);
    if (l >= x && r <= y) {
        ++seg(cur);
        return cur;
    }
    int mid = l + r >> 1;
    if (x <= mid) ls(cur) = upd(ls(pre), l, mid, x, y);
    if (y > mid) rs(cur) = upd(rs(pre), mid + 1, r, x, y);
    seg(cur) = seg(ls(cur)) + seg(rs(cur));
    return cur;
}

//区间第 k 小
int kth_small(int& pre, int& k, int l, int r, int rnk) {
    if (l == r) return l;
    int mid = l + r >> 1, cnt = seg(ls(k)) - seg(ls(pre));
    if (rnk <= cnt) return kth_small(ls(pre), lson(k), rnk);
    else return kth_small(rs(pre), rson(k), rnk - cnt);
}

//区间第 k 大
int kth_big(int& pre, int& k, int l, int r, int rnk) {
    if (l == r) return l;
    int mid = l + r >> 1, cnt = seg(rs(k)) - seg(rs(pre));
    if (rnk <= cnt) return kth_big(rs(pre), rson(k), rnk);
    else return kth_big(ls(pre), lson(k), rnk - cnt);
}

void solve() {

    int n, m;
    cin >> n >> m;
    vector<int> arr(n + 1), b(n + 1);
    for (int i = 1; i <= n; ++i) {
        cin >> arr[i];
        b[i] = arr[i];
    }

    //离散化下标
    b[0] = INT32_MIN;//初始化为负无穷 便于离散化下标从 1 开始
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    int len = b.size() - 1;

    //版本数组
    vector<int> ver(n + 1);

    //把每一个版本记录下来
    for (int i = 1; i <= n; ++i) {
        int pos = lower_bound(b.begin(), b.end(), arr[i]) - b.begin();
        //更新版本
        ver[i] = upd(ver[i - 1], 1, len, pos, pos);
    }

    int l, r, k;
    for (int i = 1; i <= m; ++i) {
        cin >> l >> r >> k;
        //获得第 k 小的数字离散化的下标
        int pos = kth_small(ver[l - 1], ver[r], 1, len, k);

        //输出第 k 小数
        cout << b[pos] << '\n';
    }
}


使用: 给出数组 <arr>,区间 <[l, r]><数字k>solve() 函数所示



5.图论

(1) Dijkstra (堆优化)


using PII = pair<int, int>;
using i64 = long long;

const int N = 2e5 + 1, inf = 1e9;

vector<PII> g[N]; //存图
vector<int> dist(N, inf); //存距离 初始化为无穷 inf

void dijkstra(int s) {
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.emplace(0, s);
    dist[s] = 0;// s 为起点

    while (heap.size()) {
        auto [dis, u] = heap.top(); heap.pop();
        if (dist[u] < dis) continue;
        for (auto& [v, w] : g[u]) {
            if (dist[v] > dis + w) {
                dist[v] = dis + w;
                heap.emplace(dist[v], v);
            }
        }
    }
}


使用: 
*dijkstra() <最短路>

-dist[idx]


(2) Spfa

- 朴素

const int N = 2e5 + 1, inf = 1e9;

vector<PII> g[N]; //存图
vector<int> dist(N, inf); //存距离 初始化为无穷 inf
bool vis[N];
int cnt[N];

void SPFA(int s) {
    queue<int> que;
    que.push(s);
    dist[s] = 0;// s 为起点

    while (que.size()) {
        auto u = que.front(); que.pop();
        vis[u] = false;
        for (auto& [v, w] : g[u]) {
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                //cnt[v] = cnt[u] + 1;
                //if (cnt[v] > n) return true; 有负环记得函数换成bool
                if (!vis[v]) {
                    vis[v] = true;
                    que.push(v);
                }
            }
        }
    }
    //return false;无负环记得函数换成bool
}


使用:
*spfa() <最短路>

-if(spfa()) <判负环>
-dist[idx]

- SLF优化

const int N = 2e5 + 1, inf = 1e9;

vector<PII> g[N]; //存图
vector<int> dist(N, inf); //存距离 初始化为无穷 inf
bool vis[N];
int cnt[N];

//Small Lable First
bool SPFA(int s) {
    deque<int> que;
    que.push_back(s);
    dist[s] = 0;// s 为起点

    while (que.size()) {
        auto u = que.front(); que.pop_front();
        vis[u] = false;
        for (auto& [v, w] : g[u]) {
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                //cnt[v] = cnt[u] + 1;
                //if (cnt[v] > n) return true; 有负环
                if (!vis[v]) {
                    vis[v] = true;
                    if (que.size() && dist[v] < dist[que.front()]) {
                        que.push_front(v);
                    } else {
                        que.push_back(v);
                    }
                }
            }
        }
    }
    //return false;无负环
}


使用:
*spfa() <最短路>

-if(spfa()) <判负环>
-dist[idx]

- LLL优化

//Large Label Last
bool spfa()
{
    //s为起点
    queue<int> que;
    que.push(s);
    dist[s] = 0;
    i64 sum = 0, num = 1;//多创建两个变量维护

    while (que.size())
    {
        int t = que.front();
        // LLL优化:队头要是比对内平均值大 先放后面 否则出队
        while (dist[t] * num > sum)
        {
            que.pop();
            que.push(t);
            t = que.front();
        }
        que.pop();
        sum -= dist[t], --num;
        //---
        visit[t] = false;
        for (int u = h[t]; u; u = ne[u])
        {
            int v = e[u];
            if (dist[v] > dist[t] + w[u])
            {
                dist[v] = dist[t] + w[u];
                //cnt[v] = cnt[t] + 1;判负环
                //if (cnt[v] > n) return true;
                if (!visit[v])
                {
                    que.push(v);
                    visit[v] = true;
                    sum += dist[v], ++num;//注意补上入队值
                }
            }
        }
    }
    //return false;判负环
}

使用:
*spfa() <最短路>

-if(spfa()) <判负环>
-dist[idx]

- SLF + LLL 优化

//Small Label First & Large Label Last
bool spfa()//老老实实用SLF好像会比SLF+LLL快
{

    deque<int> que;
    que.push_back(s);
    dist[s] = 0;
    i64 sum = dist[s], num = 1;//LLL

    while (que.size())
    {
        auto t = que.front();

        while (dist[t] * num > sum) //LLL
        {
            que.pop_front();
            que.push_back(u);
            t = que.front();
        }
        sum -= dist[t], --num;
        que.pop_front();

        visit[t] = false;
        for (int u = h[t]; u; u = ne[u])
        {
            int v = e[u];
            if (dist[v] > dist[t] + w[u])
            {
                dist[v] = dist[u] + w[u];
                //cnt[v] = cnt[t] + 1;判负环
                //if (cnt[v] > n) return true;
                if (!visit[v])
                {
                    visit[v] = true;
                    if (que.size() && dist[que.front()] > dist[v])//SLF
                        que.push_front(v);
                    else
                        que.push_back(v);
                    sum += dist[v], ++num;//LLL
                }
            }
        }
    }
    //return false;判负环
}


(3) Kruskal最小生成树


const int M = 4e5 + 1, N = 2e5 + 1;

struct Edge {
    int u, v, w;
    bool operator<(const Edge& rhs) const {
        return w < rhs.w;
    }
} edge[M];

int fa[N], n, m;// n 为点数 m 为边数

int find(int x) {
    while (fa[x] != x) x = fa[x] = fa[fa[x]];
    return x;
}

bool kruskal() {
    sort(edge, edge + m);//边存储为 [0, m - 1] 区间
    iota(fa, fa + n, 0);
    int sum = 0, cnt = 0;
    for (int i = 0; i < m; ++i) {
        int u = edge[i].u, v = edge[i].v, w = edge[i].w;
        u = find(u), v = find(v);
        if (u != v) {
            fa[u] = v;
            sum += w;
            ++cnt;
        }
    }
    return cnt == n - 1; //判断是否能构成最小生成树
    //return sum; 返回最小生成树边权和 注意改成int
}


使用:
*kruskal() <处理生成树>

-if(kruskal()) <判联通图>
-res


(4) 最近公共祖先LCA (倍增,重链剖分)

- 倍增

const int N = 2e5 + 1, logn = 21;

vector<int> g[N];//vector存图

int dep[N], nxt[N][22];//dep为深度 nxt为倍增表

void DFS(int u, int ufa) {
    dep[u] = dep[ufa] + 1, nxt[u][0] = ufa;
    for (int j = 1; (1 << j) <= dep[u]; ++j) {
        nxt[u][j] = nxt[nxt[u][j - 1]][j - 1];
    }
    for (auto& v : g[u]) {
        if (v == ufa) continue;
        DFS(v, u);
    }
}

int LCA(int u, int v) {
    if (dep[u] > dep[v]) {
        swap(u, v);
    }
    for (int j = logn; j >= 0; --j) {
        if (dep[u] - (1LL << j) >= dep[v]) {
            u = nxt[u][j];
        }
    }
    if (u == v) return u;
    for (int j = logn; j >= 0; --j) {
        if (nxt[u][j] != nxt[v][j]) {
            u = nxt[u][j];
            v = nxt[v][j];
        }
    }
    return nxt[u][0];
}


使用: 
*DFS(root, 0) <初始化 root为根>

-LCA(u, v) <获得点 u 和点 v 的LCA>

- 重链剖分

const int N = 2e5 + 1;

vector<int> g[N];//vector存图

int dep[N], fa[N], siz[N], son[N]; //DFS1相关
int dfn[N], rdfn[N], top[N], idx; //DFS2相关

void DFS1(int u, int ufa) {
    dep[u] = dep[ufa] + 1, siz[u] = 1, fa[u] = ufa;
    for (auto& v : g[u]) {
        if (v == ufa) continue;
        DFS1(v, u);
        siz[u] += siz[v];
        if (siz[v] > siz[son[u]]) {
            son[u] = v;
        }
    }
}

void DFS2(int u, int tp) {
    dfn[u] = ++idx, rdfn[idx] = u, top[u] = tp;
    if (son[u]) DFS2(son[u], tp);
    for (auto& v : g[u]) {
        if (v == fa[u] || v == son[u]) continue;
        DFS2(v, v);
    }
}

int LCA(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) {
            swap(u, v);
        }
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) {
        swap(u, v);
    }
    return u;
}


使用: 给出两个点(x, y)
*DFS1(root, 0), DFS2(root, root) <预处理 root为根>

-LCA(x, y) <求出点 u 和点 v 的LCA>


(5) 求树的重心

int cnt[N], mx[N], cent[2], n;
//cnt代表以u为节点不包含父上级的节点数
//mx代表以u为节点包含父上级的最大重量
//cnt存两个重心
inline void getcent(int u, int fa)
{
	cnt[u] = 1, mx[u] = 0;
	for (auto& x : g[u])
		if (x != fa)
			getcent(x, u),
			cnt[u] += cnt[x],
			mx[u] = max(mx[u], cnt[x]);
	mx[u] = max(mx[u], n - cnt[u]);
	if (mx[u] <= n / 2)
		cent[cent[0] != 0] = u;
}

使用: 
*getcent(1, -1) <初始化>

-cent[0], cent[1] <两个重心>


(6) Johnson全源最短路

using i64 = long long;
using PII = pair<int, int>;

//dist为正图的最短路 一维为起点 二维为终点
vector<vector<i64>> dist(N, vector<i64>(N, 0x3f3f3f3f));
vector<i64> d(N, 0x3f3f3f3f);//势能
vector<PII> g[N];//存图
int cnt[N], n, m;//n为点数 m为边数
bool vis[N];

bool spfa()//跑虚拟图
{
	queue<int> que;
	que.push(0);
	d[0] = 0;
	while (que.size())
	{   
		//t为当前点
		auto t = que.front(); que.pop();
		vis[t] = false;
		//u为邻点 w为边权
		for (auto& [u, w] : g[t])
			if (d[u] > d[t] + w)
			{
				d[u] = d[t] + w;
				cnt[u] = cnt[t] + 1;//判负环
				if (cnt[u] > n) return true;
				if (!vis[u])
					que.push(u),
					vis[u] = true;
			}
	}
	return false;
}

void dijkstra(int s)//以s为起点跑最短路
{
	for (int i = 0; i <= n; ++i) vis[i] = false;
	priority_queue<PII, vector<PII>, greater<PII>> heap;
	heap.push({ 0, s });
	dist[s][s] = 0;

	while (heap.size())
	{
		//dist为当前点距离 t为点标号
		auto [dis, t] = heap.top(); heap.pop();
		
		if (vis[t]) continue;
		vis[t] = true;
		//u为邻点 w为边权
		for (auto& [u, w] : g[t])
			if (dist[s][u] > dis + w)
				dist[s][u] = dis + w,
				heap.push({ dist[s][u], u });
	}
}

void solve()
{
	for (int i = 0, x, y, z; i < m; ++i)
	{
		cin >> x >> y >> z;
		g[x].emplace_back(y, z);
		g[y].emplace_back(x, z);
	}
	
	for (int i = 1; i <= n; ++i) //建势能图
		g[0].emplace_back(i, 0);
	
	if (spfa())//跑势能图+判负环
		return void(cout << -1 << '\n');
		
	for (int i = 1; i <= n; ++i)//重定义边权
		for(auto& [u, w] : g[i]) 
			w += d[i] - d[u];//i为起点 u为终点
	//跑n次dijkstra
	for (int i = 1; i <= n; ++i)
		dijkstra(i);

	for (int i = 1; i <= n; ++i)//输出正确的最短路
		for (int j = 1; j <= n; ++j)
			cout << dist[i][j] + d[j] - d[i] << " \n"[j == n];
}

使用:solve() 函数所示


(7) 匈牙利算法 (二分图匹配)


const int N = 2e5 + 10;
int rmatch[N], n, res = 0;//rmatch存右部匹配的左部点
vector<int> g[N];//存图
queue<int> que;//辅助清空vis
bool vis[N];

bool match(int u)
{
    for (auto& x : g[u])
    {
        if (vis[x]) continue;
        vis[x] = true, que.push(x);
        //右部没匹配 或者存在增广路则更新
        if (rmatch[x] == 0 || match(rmatch[x]))
        {
            rmatch[x] = u;
            return true;
        }
    }
    return false;
}

void hangarian()
{
    for (int i = 1; i <= n; ++i)//n是左部的点数
    {
        while (que.size()) //清空vis
            vis[que.front()] = false, que.pop();
        if (match(i)) ++res;
    }
}


使用: 
-hangarian()
-res <最大匹配数/最小点覆盖>


(8) Tarjan

- 缩点 / 强连通分量

vector<int> g[N], g2[N];//g是原图 g2是缩点图
stack<int> stk;
//low是能到达的最小dfn节点 scc是点所在强连通分量 cscc是强连通分量数
int dfn[N], low[N], scc[N], cnt, cscc, n;
//instk 点是否在栈内
bool instk[N];

void tarjan(int u)
{
	low[u] = dfn[u] = ++cnt;
	instk[u] = true;
	stk.push(u);//访问时进栈
	for (auto& x : g[u])
		if (!dfn[x])//未访问则递归继续访问
		{
			tarjan(x);
			low[u] = min(low[u], low[x]);
		}
		else if (instk[x])//访问过 且 u 可达 x
			low[u] = min(low[u], dfn[x]);
	if (low[u] == dfn[u])//记录强连通分量
	{
		++cscc;
		int top;
		do
		{
			top = stk.top();//记录时出栈
			stk.pop();
			instk[top] = false;
			scc[top] = cscc;//记录该点的强连通分量
		} while (top != u);
	}
}

void solve()
{
	//原图不一定联通 对每个未访问的点都跑一次 Tarjan
	for(int i = 1; i <= n; ++i)
		if (!dfn[i]) tarjan(i);
		
	//将强连通分量缩为一点
	for (int u = 1; u <= n; ++u)
		for (auto& x : g[u])
			if (scc[u] != scc[x])//不归属一连通分量内
				g2[scc[u]].emplace_back(scc[x]);

}

使用:solve() 函数所示

- 割点 / 割顶

vector<int> g[N];
vector<int> cut;//cut 存割点

int low[N], dfn[N], cnt, n;

void tarjan(int u, bool root = true)//root 判是否是根
{
	int tot = 0;
	dfn[u] = low[u] = ++cnt;
	for (auto& x : g[u])
	{
		if (!dfn[x])
		{
			tarjan(x, false);
			low[u] = min(low[u], low[x]);
			tot += (low[x] >= dfn[u]);//统计满足条件的子节点
		}
		else
			low[u] = min(low[u], dfn[x]);
	}
	if (tot > root)//是根则需大于1 否则大于0
		cut.emplace_back(u);
}

void solve()
{
	//原图不一定联通 对每个未访问的点都跑一次 Tarjan
	for(int i = 1; i <= n; ++i)
		if (!dfn[i]) tarjan(i);
}

使用:solve() 函数所示

-cut[x]

- 桥

using PII = pair<int, int>;

vector<PII> bridge;//存 桥
vector<int> g[N];

//此时low跑前向边时不包括fa <-> u的边
int low[N], dfn[N], fa[N], cnt, n;

void tarjan(int u)
{
	int tot = 0;
	dfn[u] = low[u] = ++cnt;
	for (auto& x : g[u])
	{
		if (!dfn[x])
		{
			fa[x] = u;//额外记录点的父亲
			tarjan(x);
			low[u] = min(low[u], low[x]);
			if (low[x] > dfn[u])
				bridge.emplace_back(u, x);
		}
		else if(fa[u] != x) //不包括父亲节点时
			low[u] = min(low[u], dfn[x]);
	}
}

void solve()
{
	//原图不一定联通 对每个未访问的点都跑一次 Tarjan
	for(int i = 1; i <= n; ++i)
		if (!dfn[i]) tarjan(i);
}

使用:solve() 函数所示

-bridge[x]

- 点双连通分量

const int N = 5e5 + 10, M = 2e6 + 10;

int h[N], e[M << 1], ne[M << 1], idx;

void add(int a, int b) {
    e[++idx] = b, ne[idx] = h[a], h[a] = idx;
}

//root:根  id:辅助dfn和low  dcc:点双数目
int dfn[N], low[N], root, id, dcc;
vector<int> vDCC[N];//存每个点双的点
stack<int> stk;//和普通tarjan同
bool cut[N];//存割点,可用set

void tarjan(int u) {
    dfn[u] = low[u] = ++id;
    stk.push(u);
    int cnt = 0;

    for (int i = h[u]; i; i = ne[i]) {
        int v = e[i];
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);

            if (dfn[u] <= low[v]) {
                ++cnt;
                if (u != root || cnt > 1) {
                    cut[u] = true;//判割点
                }
                ++dcc;//边双数量+1
                int top;
                do {
                    top = stk.top(); stk.pop();
                    vDCC[dcc].emplace_back(top);//栈内节点
                } while (top != v);
                vDCC[dcc].emplace_back(u);//当前节点也是
            }
        }
        else {
            low[u] = min(low[u], dfn[v]);
        }
    }

    //孤立点特判
    if (u == root && cnt == 0) {
        cut[u] = true;
        vDCC[++dcc].emplace_back(u);
        return;
    }
}

void solve() {

    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int x, y;
        cin >> x >> y;
        add(x, y), add(y, x);
    }

	//原图不一定联通 对每个未访问的点都跑一次 Tarjan
    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) root = i, tarjan(i);
    }

    cout << dcc << '\n';
    for (int i = 1; i <= dcc; ++i) {
        cout << vDCC[i].size() << ' ';
        for (auto& v : vDCC[i]) {
            cout << v << ' ';
        }
        cout << '\n';
    }
}

使用:solve() 函数所示

-vDCC[idx] <第 idx 个点双连通分量内的点>
-cut[idx] <判断 idx 是否是割点>


- 边双连通分量

int h[N], e[M << 1], ne[M << 1], idx = 1;

void add(int a, int b) {
    e[++idx] = b, ne[idx] = h[a], h[a] = idx;
}

//root:根  id:辅助dfn和low  dcc:边双数目
int dfn[N], low[N], root, id, dcc;
vector<int> eDCC[N];//存每个边双的点
stack<int> stk;//和普通tarjan同
bool bridge[M << 1];//存桥

void tarjan(int u, int from) {
    dfn[u] = low[u] = ++id;
    stk.push(u);
    for (int i = h[u]; i; i = ne[i]) {
        int v = e[i];
        if (!dfn[v]) {
            tarjan(v, i);
            low[u] = min(low[u], low[v]);
            if (dfn[u] < low[v]) {
                bridge[i] = bridge[i ^ 1] = true;//判桥
            }
        }
        else if ((i ^ 1) != from) {
            low[u] = min(low[u], dfn[v]);
        }
    }

    if (dfn[u] == low[u]) {
        ++dcc;
        int top;
        do {
            top = stk.top(); stk.pop();
            eDCC[dcc].emplace_back(top);
        } while (top != u);
    }
}

void solve() {

    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int x, y;
        cin >> x >> y;
        add(x, y), add(y, x);
    }

    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) root = i, tarjan(i, 0);
    }

    cout << dcc << '\n';
    for (int i = 1; i <= dcc; ++i) {
        cout << eDCC[i].size() << ' ';
        for (auto& v : eDCC[i]) {
            cout << v << ' ';
        }
        cout << '\n';
    }
}

使用:solve() 函数所示

-vDCC[idx] <第 idx 个边双连通分量内的点>
-bridge[idx] <判断第 idx 编号的边是否是割边>




6.网络流

(1) 网络最大流

- Dinic(当前弧优化)

#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;

using u64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;

const int N = 1e4 + 10, M = 2e5 + 10;

struct Edge {
    //点 下一点 流量(根据题目决定longlong或int)
    int v, nxt; i64 f;
}E[M];//存边,双向边注意M的范围

int h[N], n, m, S, T, idx = 1;//注意idx初始化为1

int dist[N], cur[N];

bool BFS() {
    //看好范围,一定要将所有点初始化成正无穷
    fill(dist, dist + n + 1, 0x3f3f3f3f);
    queue<int> que;
    que.push(S);
    dist[S] = 0, cur[S] = h[S];
    while (que.size()) {
        int u = que.front(); que.pop();
        for (int i = h[u]; i; i = E[i].nxt) {
            auto& [v, nxt, f] = E[i];
            if (f && dist[v] > dist[u] + 1) {
                dist[v] = dist[u] + 1;
                cur[v] = h[v];//更新当前弧
                if (v == T) return true;//找到一条增广路可以直接退出
                que.push(v);
            }
        }
    }
    return false;
}

i64 DFS(int u = S, i64 flow = INT64_MAX) {
    if (u == T) return flow;
    i64 last = flow;
    for (int i = cur[u]; i && last; i = E[i].nxt) {
        cur[u] = i;//当前弧优化
        auto& [v, nxt, f] = E[i];
        if (f && dist[v] == dist[u] + 1) {
            i64 cost = DFS(v, min(f, last));
            if (!cost) dist[v] = 0x3f3f3f3f;//废点优化
            E[i].f -= cost, E[i ^ 1].f += cost;
            last -= cost;
        }
    }
    return flow - last;
}

//Maxflow
i64 MF = 0;
void Dinic() {
    while (BFS()) {
        i64 flow = DFS();
        MF += flow;
    }
}

void add(int u, int v, i64 f) {
    E[++idx] = { v, h[u], f }, h[u] = idx;
}

void solve() {

    cin >> n >> m >> S >> T;
    for (int i = 1; i <= m; ++i) {
        int u, v; i64 f;
        cin >> u >> v >> f;
        add(u, v, f);//注意加边问题
        add(v, u, 0);
    }

    Dinic();

    cout << MF << '\n';
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}


使用:
如 solve() 函数所示

- ISAP(当前弧优化)

using i64 = long long;

const int N = 1e5 + 10;

int h[N], e[N << 1], ne[N << 1], idx = 1;
//gap层点数 dist 分层 cur 当前弧优化
int gap[N], cur[N], dist[N], s, t;
i64 w[N << 1];//流量

int n, m;

inline void bfs()
{
    for (int i = 1; i <= n; ++i)
        dist[i] = -1, gap[i] = 0;
    queue<int> que;
    dist[t] = 0, gap[0] = 1;
    que.push(t);
    while (que.size())
    {
        auto u = que.front(); que.pop();
        for (int i = h[u]; i; i = ne[i])
        {
            int v = e[i];
            if (dist[v] != -1) continue;
            dist[v] = dist[u] + 1, que.push(v);
            ++gap[dist[v]];
        }
    }
}

i64 dfs(int u = s, i64 flow = INT64_MAX)
{
    if (u == t) return flow;
    i64 last = flow;//该点剩余的流

    for (int i = h[u]; i; i = ne[i])
    {
        i64 v = e[i], vol = w[i];
        if (vol > 0 && dist[u] == dist[v] + 1)
        {
            i64 c = dfs(v, min(vol, last));
            w[i] -= c, w[i ^ 1] += c;//正边加 反边减
            last -= c;//残余流量
        }
        if (last == 0) return flow;//所有流量均用光
    }
    --gap[dist[u]];
    if (!gap[dist[u]]) 
        dist[s] = n + 1;
    ++dist[u], ++gap[dist[u]];
    return flow - last;
}

inline i64 ISAP()
{
    bfs();
    i64 mxflow = 0;
    while (dist[s] < n)
    {
        for (int i = 1; i <= n; ++i) cur[i] = h[i];
        mxflow += dfs();
    }
    return mxflow;
}


inline void add(int a, int b, i64 c)
{
    e[++idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx;
}

void solve()
{
    cin >> n >> m >> s >> t;
    for (int i = 0, x, y, w; i < m; ++i)
    {
        cin >> x >> y >> w;
        add(x, y, w), add(y, x, 0);
    }
    cout << ISAP() << '\n';
}


使用:
如 solve() 函数所示


(2) 最小费用最大流

- Dinic + SPFA


const int N = 1e4 + 10, M = 1e5 + 10;

struct Edge {
    //点 下一点 流量 花费(根据题目决定longlong或int)
    int v, nxt, f, w;
}E[M];//存边,双向边注意M的范围

int h[N], n, m, S, T, idx = 1;//注意idx初始化为1

int dist[N], cur[N];
bool vis[N];

bool SPFA() {
    //看好范围,一定要将所有点初始化成正无穷
    fill(dist, dist + n + 1, 0x3f3f3f3f);
    queue<int> que;
    que.push(S);
    dist[S] = 0, cur[S] = h[S];
    while (que.size()) {
        int u = que.front(); que.pop();
        vis[u] = false;
        for (int i = h[u]; i; i = E[i].nxt) {
            auto& [v, nxt, f, w] = E[i];
            if (f && dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                if (!vis[v]) {
                    vis[v] = true;
                    cur[v] = h[v];//更新当前弧
                    que.push(v);
                }
            }
        }
    }
    return dist[T] != 0x3f3f3f3f;
}

int DFS(int u = S, int flow = INT32_MAX) {
    if (u == T) return flow;
    int last = flow;
    vis[u] = true;//不走0环 注意标记
    for (int i = cur[u]; i && last; i = E[i].nxt) {
        cur[u] = i;//当前弧优化
        auto& [v, nxt, f, w] = E[i];
        if (f && !vis[v] && dist[v] == dist[u] + w) {
            int cost = DFS(v, min(f, last));
            if (!cost) dist[v] = 0x3f3f3f3f;//废点优化
            E[i].f -= cost, E[i ^ 1].f += cost;
            last -= cost;
        }
    }
    vis[u] = false;//标记清除
    return flow - last;
}

//MinCost Maxflow
int MC = 0, MF = 0;
void MCMF() {
    while (SPFA()) {
        int flow = DFS();
        MF += flow;
        MC += dist[T] * flow;
    }
}

void add(int u, int v, int f, int w) {
    E[++idx] = { v, h[u], f, w }, h[u] = idx;
}

void solve() {

    int n, m, S, T;
    cin >> n >> m >> S >> T;
    for (int i = 1; i <= m; ++i) {
        int u, v, f, w;
        cin >> u >> v >> f >> w;
        add(u, v, f, w);//注意加边问题
        add(v, u, 0, -w);
    }

    MCMF();

    cout << MF << ' ' << MC << '\n';
}

使用:
如 solve() 函数所示



7.字符串

(1) KMP字符串匹配

string str, match;//str是模式串 match是匹配串

void getnxt(vector<int>& nxt, int len, string matcht)
{
	for (int i = 2; i < len; ++i)
	{
		nxt[i] = nxt[i - 1];
		while (nxt[i] && matcht[i] != matcht[nxt[i] + 1]) nxt[i] = nxt[nxt[i]];
		nxt[i] += (matcht[i] == matcht[nxt[i] + 1]);//补正nxt[i]
	}
}

bool kmp(string str, string match)
{
	//使字符串下标从 1 开始
	string strt = '?' + str + str, matcht = '?' + match;
	vector<int> nxt(matcht.size());
	getnxt(nxt, matcht.size(), matcht);
	//开始匹配: i为str下标 j 为match下标
	for (int i = 1, j = 1; i <= strt.size();)
	{
		while (j != 1 && strt[i] != matcht[j]) j = nxt[j - 1] + 1;//回溯 j
		if (strt[i] == matcht[j]) ++i, ++j;
		else ++i;
		if (j == matcht.size())
		{
			//此时 i - match.size() 即为第一个开始匹配元素的下标
			j = nxt[match.size()] + 1;
			//匹配成功后要做的事
			//return true;
		}
	}
	//匹配失败后要做的事
	//return false;
}

使用: 
-kmp(str, match) (模式串, 匹配串)
-if(kmp(str, match)) <判断>

注意:如果要多个模式串匹配一个匹配串,请务必开一个全局nxt数组保证复杂度


(2)Trie 字典树


const int N = 3e6 + 10, M = 26 + 26 + 10 + 10;//N 字符串总长度 M 字符种类数目
int trie[N][M], cnt[N], idx = 0;
bool exist[N];

void clear()
{
	fill(trie[0], trie[0] + idx * M, 0);
	fill(exist, exist + idx + 1, false);
	fill(cnt, cnt + idx + 1, 0);
	idx = 0;
}
int pos(char x)
{
	if (x >= 'a' && x <= 'z') return x - 'a' + 1;
	if (x >= 'A' && x <= 'Z') return x -'A' + 27;
	if (x >= '0' && x <= '9') return x -'0' + 53;
}
void insert(string & str)//插入字符串 str
{
	int now = 0;
	for (auto& x : str)
	{
		if (!trie[now][pos(x)]) trie[now][pos(x)] = ++idx;
		now = trie[now][pos(x)];
		++cnt[now];
	}
	exist[now] = true;
}
int findpre(string & str)//查询有多少个相同前缀 str
{
	int now = 0;
	for (auto& x : str)
	{
		if (!trie[now][pos(x)]) return 0;
		now = trie[now][pos(x)];
	}
	return cnt[now];
}
bool findstr(string & str)//查询是否存在字符串 str
{
	int now = 0;
	for (auto& x : str)
	{
		if (!trie[now][pos(x)]) return false;
		now = trie[now][pos(x)];
	}
	return exist[now];
}


使用: 给出字符串 <str>

-clear() <清空字典树>
-insert(str) <向树上插入str字符串>
-findpre(str) <查询是否有前缀串str>
-findstr(str) <查询是否存在字符串str>


(3) 字符串双哈希


using ui64 = unsigned long long;
using PUU = pair<ui64, ui64>;
using i64 = long long;

// N 为字符串最长长度 p为seed  mod1/mod2 为两个模数
const ui64 N = 1e5 + 10, p = 131, mod1 = 998244853, mod2 = 1e9 + 7;

ui64 a1[N], a2[N], hs1[N], hs2[N];

void init() {//初始化 a 数组
	a1[0] = a2[0] = 1;
	for (int i = 1; i < N; ++i) {
		a1[i] = a1[i - 1] * p % mod1;
		a2[i] = a2[i - 1] * p % mod2;
	}
}

void hashstr(string& str) {//将str哈希化
	int n = str.size();
	//默认str下标从0开始 如果从1开始则需要修改str[i - 1]为str[i]
	for (int i = 1; i <= n; ++i) {
		hs1[i] = (hs1[i - 1] * p % mod1 + str[i - 1]) % mod1;
		hs2[i] = (hs2[i - 1] * p % mod2 + str[i - 1]) % mod2;
	}
}

ui64 geths1(int l, int r) {//得到str[l -- r]的第一哈希值 定义域[1, n]
	return (hs1[r] - hs1[l - 1] * a1[r - l + 1] % mod1 + mod1) % mod1;
}

ui64 geths2(int l, int r) {//得到str[l -- r]的第二哈希值 定义域[1, n]
	return (hs2[r] - hs2[l - 1] * a2[r - l + 1] % mod2 + mod2) % mod2;
}

使用: 给出一个字符串 str,且 len 为 str 的长度
*init() <初始化预处理>
*hashstr(str) <将字符串str哈希化预处理>

-hs1[len] / hs2[len] <str整串的第一/二哈希值>
-geths1(l, r) / geths2(l, r) <得到str[l--r]的第一/二哈希值> 


<将哈希值存入 map<PUU, int>s, set<PUU> s 进一步处理>
例如:
map[{ geths1(l, r), geths2(l, r) }];
set,insert({ geths1(l, r), geths2(l, r) });
(注意: l,r严格在闭区间[1, n])



8.杂项

(1) 快读快写模板

template<class T> void read(T& x) {
    x = 0; int f = 1; char ch = getchar();
    while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
    while (isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
    x *= f;
}

template<class T> void print(T x) {
    if (x < 0) { putchar('-'); x = -x; }
    if (x > 9) print(x / 10);
    putchar(x % 10 ^ 48);
}

使用:给出变量 a

-read(a)
-print(a)


正在慢慢学,学会了再来更新
update: 2023 / 1 / 17 创建
update: 2023 / 1 / 20 偷取:jiangly神の模板元
update: 2023 / 1 / 23 更新:重链剖分 // LCA (重链剖分)// 单调队列
update: 2023 / 2 / 04 更新:分块算法(维护区间和) // 二分
update: 2023 / 2 / 06 更新:并查集( jiangly神的板子)
update: 2023 / 2 / 18 更新:匈牙利算法(二分图匹配)
update: 2023 / 2 / 20 更新:Tarjan(缩点 / 强连通分量)
update: 2023 / 2 / 21 更新:Tarjan(割点 / 割顶 // 桥)
update: 2023 / 2 / 21 更新:Trie 字典树
update: 2023 / 2 / 24 偷取:jiangly神の组合数板子
update: 2023 / 2 / 27 偷取:yggの组合数板子
update: 2023 / 3 / 06 更新:网络最大流 Dinic / ISAP
update: 2023 / 3 / 27 更新:字符串双哈希
update: 2023 / 4 / 08 更新:快读快写模板
update: 2023 / 4 / 18 更新:可持久化线段树(维护区间和)
update: 2023 / 4 / 19 更新:珂朵莉树(ODT)
update: 2023 / 5 / 9 更新:莫队板子
update: 2023 / 5 / 10 更新:线性筛欧拉函数
update: 2023 / 5 / 12 更新:点双连通分量 // 边双连通分量
update: 2023 / 9 / 5 更新:类模板
update: 2023 / 11 / 27 更新:更新了一部分较老模板

1 图论 3 1.1 术语 3 1.2 独立集、覆盖集、支配集之间关系 3 1.3 DFS 4 1.3.1 割顶 6 1.3.2 桥 7 1.3.3 强连通分量 7 1.4 最小点基 7 1.5 拓扑排序 7 1.6 欧拉路 8 1.7 哈密顿路(正确?) 9 1.8 Bellman-ford 9 1.9 差分约束系统(用bellman-ford解) 10 1.10 dag最短路径 10 1.11 二分图匹配 11 1.11.1 匈牙利算法 11 1.11.2 KM算法 12 1.12 网络流 15 1.12.1 最大流 15 1.12.2 上下界的网络的最大流 17 1.12.3 上下界的网络的最小流 17 1.12.4 最小费用最大流 18 1.12.5 上下界的网络的最小费用最小流 21 2 数论 21 2.1 最大公约数gcd 21 2.2 最小公倍数lcm 22 2.3 快速幂取模B^LmodP(O(logb)) 22 2.4 Fermat小定理 22 2.5 Rabin-Miller伪素数测试 22 2.6 Pollard-rho 22 2.7 扩展欧几里德算法extended-gcd 24 2.8 欧拉定理 24 2.9 线性同余方程ax≡b(mod n) 24 2.10 中国剩余定理 25 2.11 Discrete Logging(BL == N (mod P)) 26 2.12 N!最后一个不为0的数字 27 2.13 2^14以内的素数 27 3 数据结构 31 3.1 堆(最小堆) 31 3.1.1 删除最小值元素: 31 3.1.2 插入元素和向上调整: 32 3.1.3 堆的建立 32 3.2 并查集 32 3.3 树状数组 33 3.3.1 LOWBIT 33 3.3.2 修改a[p] 33 3.3.3 前缀和A[1]+…+A[p] 34 3.3.4 一个二维树状数组的程序 34 3.4 线段树 35 3.5 字符串 38 3.5.1 字符串哈希 38 3.5.2 KMP算法 40 4 计算几何 41 4.1 直线交点 41 4.2 判断线段相交 41 4.3 三点外接圆圆心 42 4.4 判断点在多边形内 43 4.5 两圆交面积 43 4.6 最小包围圆 44 4.7 经纬度坐标 46 4.8 凸包 46 5 Problem 48 5.1 RMQ-LCA 48 5.1.1 Range Minimum Query(RMQ) 49 5.1.2 Lowest Common Ancestor (LCA) 53 5.1.3 Reduction from LCA to RMQ 56 5.1.4 From RMQ to LCA 57 5.1.5 An<O(N), O(1)> algorithm for the restricted RMQ 60 5.1.6 An AC programme 61 5.2 最长公共子序列LCS 64 5.3 最长上升子序列/最长不下降子序列(LIS) 65 5.3.1 O(n^2) 65 5.3.2 O(nlogn) 66 5.4 Joseph问题 67 5.5 0/1背包问题 68 6 组合数学相关 69 6.1 The Number of the Same BST 69 6.2 排列生成 71 6.3 逆序 72 6.3.1 归并排序求逆序 72 7 数值分析 72 7.1 二分法 72 7.2 迭代法(x=f(x)) 73 7.3 牛顿迭代 74 7.4 数值积分 74 7.5 高斯消元 75 8 其它 77
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. 最小点基,最小权点基
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值