个人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 更新:更新了一部分较老模板

  • 11
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

柠檬味的橙汁

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

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

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

打赏作者

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

抵扣说明:

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

余额充值