ACM常用算法模板总结ver2.0


暑假更新了一些数据结构和字符串。

[TOC]

1 数据结构

1.1 并查集

int p[N]; //存储每个点的祖宗节点
// int size[N]; 维护size

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
void init (int x) {
	for (int i = 0; i <= x; i ++ ) {
        p[i] = i;
        // size[i] = 1; 维护size
    }
}

// 合并a和b所在的两个集合:
	p[find(a)] = find(b);
	// size[b] += size[a];维 护size

1.2 单调栈

找出每个数左边离它最近的比它大/小的数
stack<int> s;
for (int i = 1; i <= n; i ++ )
{
	while (sz(s) && check(s.top(), i)) s.pop();
	s.push(i);
}

1.3 单调队列

// 滑动窗口
int a[N], q[N]; // q为单调队列,需要注意,队列中存放的是数组下标。

int main()
{
    int n, k;
    //n个数,窗口长度为k
    cin >> n >> k;

    for (int i = 0; i < n; i ++ ) cin >> a[i];

    int hh = 0, tt = -1; // hh为队列头,tt为队列尾。tt < hh的原因是防止在刚进入循环时判断出错,所以要将tt小于hh,从而达到先入一个数的情况。
    for (int i = 0; i < n; i ++ ){ // 模拟队尾碰到新数a[i]的过程
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ; // 单调队列长度大于k,队头出队,加上hh <= tt的原因主要是防止初始状态时tt < hh。

        while (hh <= tt && a[q[tt]] >= a[i]) tt --; // 碰到新数a[i]后,若当前队尾大于a[i],则放入后不满足单调递减
        q[ ++ tt] = i; // 新元素入队

        if (i >= k - 1) printf("%d ", a[q[hh]]); // 用单调队列维护一个单调递减的区间,故每次窗口内最大值一定是队头
    }

    printf("\n");
	
    // 以下同上,改为维护单调递增即可。
    hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ ){
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;

        while (hh <= tt && a[q[tt]] <= a[i]) tt --;
        q[ ++ tt] = i;

        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    printf("\n");

    return 0;
}

1.4 树状数组

1.4.1 单点修改与区间查询

int t[maxn];

int lowbit (int x) {
	return x & -x;
}

void add(int x, int k){ // 单点修改, 可用于添数或建树
	for (; x <= n; x += x & -x) t[x] += k;
}

int query(int x){ // 区间查询 (1 ~ x)
	int ans = 0;
	for (; x; x -= x & -x) ans += t[x];
	return ans;
}

int range (int l, int r) { // 区间查询 (l ~ r)
	return query(r) - query(l - 1);
}

1.4.2 区间修改与单点查询

int t[maxn], q[maxn]; // 用t数组维护差分数组

int lowbit (int x) {
	return x & -x;
}

void add(int x, int k){ 
	for (; x <= n; x += x & -x) t[x] += k;
}

int query(int x){
	int ans = 0;
	for (; x; x -= x & -x) ans += t[x];
	return ans;
}

int range (int l, int r) {
	return query(r) - query(l - 1);
}

signed main()
{
    cin >> n >> p;
    for (int i = 1; i <= n; i ++ ){
        cin >> q[i];
        add(i, q[i] - q[i - 1]);
    }
    
    while (p -- ){
        int op, l, r, x;
        cin >> op;
        if (op == 1){
            cin >> l >> r >> x;
            add(l, x), add(r + 1, -x);
        }
        if (op == 2){
            cin >> x;
            cout << q[x] + ask(x) << endl; // ask(x)即为q[x]的增量
        }
    }

    return 0;
}

1.4.3 区间修改与区间查询

//区间修改与区间查询
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, p;
int q[1123456], t[1123456], s[1123456], sum[1123456];

inline int lowbit(int x)
{
    return x & -x;
}

void add1(int x, int k)
{
    for (int i = x; i <= n; i += lowbit(i)) t[i] += k, s[i] += x * k;
}

int ask1(int x)
{
    int ans = 0;
    for (int i = x; i; i -= lowbit(i)) ans += (x + 1) * t[i] - s[i];
    return ans;
}

signed main()
{
    cin >> n >> p;
    for (int i = 1; i <= n; i ++ ){
        cin >> q[i];
        sum[i] = q[i] + sum[i - 1];
        add1(i, q[i] - q[i - 1]);
    }
    
    while (p -- ){
        int op, l, r, x;
        cin >> op;
        if (op == 1){
            cin >> l >> r >> x;
            add1(l, x), add1(r + 1, -x);
        }
        if (op == 2){
            cin >> l >> r;
            cout << ask1(r) - ask1(l - 1) << endl;
        }
    }

    return 0;
}

1.5 线段树

// pushup
void pushup1(int u) {
	tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
} // 父节点的区间和等于左右儿子的区间和。
void pushup2(int u) {
    tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
} // 父节点的最大值等于左右儿子的最大值。


// pushdown
void pushdown(int u) // 例为维护区间加的lazytag
{
    auto &r = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (r.add){
        left.add += r.add, left.sum += (left.r - left.l + 1) * r.add;
        right.add += r.add, right.sum += (right.r - right.l + 1) * r.add;
        r.add = 0;
    }
}

// build
void build (int u, int l, int r) {
	if (l == r) tr[u] = {l, r, q[r]};
	else {
		tr[u] = {l, r, q[r], 0}; // 该节点的左端点,右端点,以及维护的值和lazytag(可以不为1)
		int mid = l + r >> 1;
		build(u << 1, l, mid); // 向左下递归建树
		build(u << 1 | 1, mid + 1, r); // 向右下递归建树
		pushup(u); // 最后自底向上更新节点所维护的值
	}
}


//modify

// 1.单点修改
void modify1(int u, int x, int v) // x为待修改的数的下标,v为修改后的值
{
    if (tr[u].l == x && tr[u].r == x) tr[u].v = v; // 修改
    else{ // 未达到就继续递归
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u); // 修改后自底向上更新一下。
    }
}

// 2.区间修改(需要lazytag)
void modify2(int u, int l, int r, int d) {
	if (tr[u].l >= l && tr[u].r <= r) {
		tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
		tr[u].add += d;
	}
	else {
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) modify(u << 1, l, r, d);
		if (r > mid) modify(u << 1 | 1, l, r, d);
		pushup(u);
	}
}


//query
int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;

    pushdown(u); // 如果有则加
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if (l <= mid) sum += query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

1.6 对顶堆

int q[i];

priority_queue<int> bg; // 大根堆
priority_queue<int, vector<int>, greater<int>> ss; // 小根堆

//bg.push(q[1]);

int mid = q[1];
cout << mid << endl;
for (int i = 2; i <= n; i ++ ) {
    if (q[i] > mid) ss.push(q[i]);
    else bg.push(q[i]);

    if (i & 1) { // 当i为奇数输出中位数
        while (bg.size() != ss.size()) {
            if (bg.size() > ss.size()) {
                ss.push(mid);v,
                mid = bg.top();
                bg.pop();
            }
            else {
                bg.push(mid);
                mid = ss.top();
                ss.pop();
            }
        }
        cout << mid << endl;
    }
}

1.7 主席树

#include <bits/stdc++.h>
using namespace std;

const int maxn = 100010;

int n, m, len;
int q[maxn], a[maxn];
int root[maxn], now = 0;

struct node {
        int l, r, v;
}tr[maxn * 20];

int build (int l, int r) {
        int p = ++ now, mid = (l + r) >> 1;
        if (l < r) {
                tr[p].l = build(l, mid);
                tr[p].r = build(mid + 1, r);
        }
        tr[p].v = 0;
        return p;
}

int update (int pre, int l, int r, int v) {
        int p = ++ now, mid = (l + r) >> 1;
        tr[p].l = tr[pre].l, tr[p].r = tr[pre].r, tr[p].v = tr[pre].v + 1;
        if (l < r) {
                if (v <= mid) tr[p].l = update(tr[pre].l, l, mid, v);
                else tr[p].r = update(tr[pre].r, mid + 1, r, v);
        }
        return p;
}

int query (int x, int y, int l, int r, int k) {
        if (l == r) return l;
        
        int sum = tr[tr[y].l].v - tr[tr[x].l].v, mid = (l + r) >> 1;
        if (k <= sum) return query(tr[x].l, tr[y].l, l, mid, k);
        else return query(tr[x].r, tr[y].r, mid + 1, r, k - sum);
}

int main () {
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= n; i ++ ) {
                scanf("%d", &q[i]);
                a[i] = q[i];
        }
        
        sort(q + 1, q + n + 1);
        len = unique(q + 1, q + n + 1) - (q + 1);
        for (int i = 1; i <= n; i ++ ) {
                a[i] = lower_bound(q + 1, q + len + 1, a[i]) - q;
        }
        root[0] = build(1, len);
        
        for (int i = 1; i <= n; i ++ ) {
                root[i] = update(root[i - 1], 1, len, a[i]);
        }
        
        while (m -- ) {
                int l, r, k;
                scanf("%d %d %d", &l, &r, &k);
                int ans = query(root[l - 1], root[r], 1, len, k);
                printf("%d\n", q[ans]);
        }
}

1.8 Treap

1.8.1 旋转Treap

int tt;
int n, m, k;
int now, root;
int sz[maxn], key[maxn], cnt[maxn], rd[maxn], son[maxn][2];

inline void push_up(int x) {
	sz[x] = sz[son[x][0]] + sz[son[x][1]] + cnt[x];
}

inline void rotate (int &x, int y) {
	int ii = son[x][y ^ 1];
	son[x][y ^ 1] = son[ii][y];
	son[ii][y] = x;
	push_up(x), push_up(ii);
	x = ii;
}

void insert (int &u, int x) {
	if (!u) {
		u = ++ now;
		sz[u] = cnt[u] = 1;
		key[u] = x;
		rd[u] = rand();
		return;
	}
	if (key[u] == x) {
		cnt[u] ++ ;
		sz[u] ++ ;
		return;
	}
	int d = (x > key[u]);
	insert(son[u][d], x);
	if (rd[u] < rd[son[u][d]]) {
		rotate(u, d ^ 1);
	}
	push_up(u);
}

void del (int &u, int x) {
	if (!u) return;
	if (x != key[u]) {
		del (son[u][x > key[u]], x);
	}
	else {
		if (!son[u][0] && !son[u][1]) {
			cnt[u] -- ;
			sz[u] -- ;
			if (cnt[u] == 0) u = 0;
		}
		else if (son[u][0] && !son[u][1]) {
			rotate(u, 1);
			del (son[u][1], x);
		}
		else if (!son[u][0] && son[u][1]) {
			rotate(u, 0);
			del (son[u][0], x);
		}
		else {
			int d = rd[son[u][0]] > rd[son[u][1]];
			rotate(u, d);
			del(son[u][d], x);
		}
	}
	push_up(u);
}

int get_rank(int u, int x) { // 得到排名
	if (!u) return 0;
	if (key[u] == x) return sz[son[u][0]] + 1;
	if (key[u] < x) return sz[son[u][0]] + cnt[u] + get_rank(son[u][1], x);
	return get_rank(son[u][0], x);
}

int find (int u, int x) { // 查询排名
	if (!u) return 0;
	if(sz[son[u][0]] >= x) {
		return find(son[u][0], x);
	}
	else if (sz[son[u][0]] + cnt[u] < x) {
		return find(son[u][1], x - cnt[u] - sz[son[u][0]]);
	}
	else return key[u];
}

int pre (int u, int x) { // 查询前驱
	if (!u) return -inf;
	if (key[u] >= x) {
		return pre(son[u][0], x);
	}
	else return max(key[u], pre(son[u][1], x));
}

int suf (int u, int x) { // 查询后继
	if (!u) return inf;
	if (key[u] <= x) {
		return suf(son[u][1], x);
	}
	else return min(key[u], suf(son[u][0], x));
}

void solve() {
	int query;
	cin >> query;
	while (query -- ) {
		int op, x;
		cin >> op >> x;
		if (op == 1) insert(root, x);
		if (op == 2) del(root, x);
		if (op == 3) cout << get_rank(root, x) << endl;
		if (op == 4) cout << find(root, x) << endl;
		if (op == 5) cout << pre(root, x) << endl;
		if (op == 6) cout << suf(root, x) << endl;
	}
}

1.8.2 Fhq Treap

mt19937 rnd(233);
int root, idx;
int x, y, z;
struct fhq {
        int l, r;
        int key, val; // key权值,val堆值
        int size;
}tr[maxn];

inline int get_node (int key) {
        tr[ ++ idx].key = key;
        tr[idx].val = rnd();
        tr[idx].size = 1;
        return idx;
}

inline void pushup (int u) {
        tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + 1;
}

inline void split (int u, int k, int &x, int &y) {
        if (!u) x = y = 0;
        else {
                if (tr[u].key <= k) {
                        x = u;
                        split(tr[u].r, k, tr[u].r, y);
                }
                else {
                        y = u;
                        split(tr[u].l, k, x, tr[u].l);
                }
                pushup(u);
        }
}

inline int merge (int x, int y) {
        if (!x || !y) return x + y;
        if (tr[x].val > tr[y].val) {
                tr[x].r = merge(tr[x].r, y);
                pushup(x); return x;
        }
        else {
                tr[y].l = merge(x, tr[y].l);
                pushup(y); return y;
        }
}

inline void insert (int k) {
        split (root, k, x, y);
        root = merge(merge(x, get_node(k)), y);
}

inline void del (int k) {
        split (root, k, x, z);
        split (x, k - 1, x, y);
        y = merge(tr[y].l, tr[y].r);
        root = merge(merge(x, y), z);
}

inline int get_rank (int k) {
        split (root, k - 1, x, y);
        k = tr[x].size + 1;
        root = merge(x, y);
        return k;
}

inline int get_key (int k) {
        int p = root;
        while (p) {
                if (tr[tr[p].l].size + 1 == k) {
                        break;
                }
                else if (tr[tr[p].l].size >= k) {
                        p = tr[p].l;
                }
                else {
                        k -= tr[tr[p].l].size + 1;
                        p = tr[p].r;
                }
        }
        return tr[p].key;
}

inline int pre (int k) {
        split (root, k - 1, x, y);
        int p = x;
        while (tr[p].r) p = tr[p].r;
        k = tr[p].key;
        root = merge(x, y);
        return k;
}

inline int suf (int k) {
        split (root, k, x, y);
        int p = y;
        while (tr[p].l) p = tr[p].l;
        k = tr[p].key;
        root = merge(x, y);
        return k;
}

void solve() {
        read(n);
        for (int i = 1; i <= n; i ++ ) {
                int op, x; read(op); read(x);
                if (op == 1) insert(x);
                else if (op == 2) del(x);
                else if (op == 3) printf("%lld\n", get_rank(x));
                else if (op == 4) printf("%lld\n", get_key(x));
                else if (op == 5) printf("%lld\n", pre(x));
                else printf("%lld\n", suf(x));
        }
}


// 区间反转版本
mt19937 rnd(233);
int root, idx;
int x, y, z;
struct fhq {
        int l, r;
        int key, val; // key权值,val堆值
        int size;
        int tag;
}tr[maxn];

inline int get_node (int key) {
        tr[ ++ idx].key = key;
        tr[idx].val = rnd();
        tr[idx].size = 1;
        tr[idx].tag = 0;
        return idx;
}

inline void pushup (int u) {
        tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + 1;
}

inline void pushdown (int u) {
        if (tr[u].tag) {
                swap(tr[u].l, tr[u].r);
                tr[tr[u].l].tag ^= 1, tr[tr[u].r].tag ^= 1;
                tr[u].tag = 0;
        }
}

inline void split (int u, int k, int &x, int &y) {
        if (!u) x = y = 0;
        else {
                pushdown(u);
                if (tr[tr[u].l].size + 1 <= k) {
                        x = u;
                        split(tr[u].r, k - tr[tr[u].l].size - 1, tr[u].r, y);
                }
                else {
                        y = u;
                        split(tr[u].l, k, x, tr[u].l);
                }
                pushup(u);
        }
}

inline int merge (int x, int y) {
        if (!x || !y) return x + y;
        if (tr[x].val > tr[y].val) {
                pushdown(x);
                tr[x].r = merge(tr[x].r, y);
                pushup(x); return x;
        }
        else {
                pushdown(y);
                tr[y].l = merge(x, tr[y].l);
                pushup(y); return y;
        }
}

inline void insert (int k) {
        split (root, k, x, y);
        root = merge(merge(x, get_node(k)), y);
}

inline void del (int k) {
        split (root, k, x, z);
        split (x, k - 1, x, y);
        y = merge(tr[y].l, tr[y].r);
        root = merge(merge(x, y), z);
}

inline int get_rank (int k) {
        split (root, k - 1, x, y);
        k = tr[x].size + 1;
        root = merge(x, y);
        return k;
}

inline int get_key (int k) {
        int p = root;
        while (p) {
                if (tr[tr[p].l].size + 1 == k) {
                        break;
                }
                else if (tr[tr[p].l].size >= k) {
                        p = tr[p].l;
                }
                else {
                        k -= tr[tr[p].l].size + 1;
                        p = tr[p].r;
                }
        }
        return tr[p].key;
}

inline int pre (int k) {
        split (root, k - 1, x, y);
        int p = x;
        while (tr[p].r) p = tr[p].r;
        k = tr[p].key;
        root = merge(x, y);
        return k;
}

inline int suf (int k) {
        split (root, k, x, y);
        int p = y;
        while (tr[p].l) p = tr[p].l;
        k = tr[p].key;
        root = merge(x, y);
        return k;
}

inline void print (int u) {
        if (!u) return;
        pushdown(u);
        print(tr[u].l); printf("%lld ", tr[u].key); print(tr[u].r);
}

void solve() {
        read(n), read(m);
        // insert(-inf); insert(inf);
        for (int i = 1; i <= n; i ++ ) insert(i);

        while (m -- ) {
                int l, r; read(l); read(r);
                split (root, l - 1, x, y);
                split (y, r - l + 1, y, z);
                tr[y].tag ^= 1;
                root = merge(x, merge(y, z));
        }
        print(root);
}

1.9 轻重链剖分

int h[maxn], ne[maxn], e[maxn], idx;
int w[maxn], wt[maxn], id[maxn], top[maxn], fa[maxn], depth[maxn], sz[maxn], son[maxn];
int tt;
int n, m, k;
int root, mod, cnt;

struct node {
	int l, r;
	int sum, add;
}tr[maxn << 2];

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

void dfs1 (int u, int f) {
	depth[u] = depth[f] + 1;
	fa[u] = f;
	sz[u] = 1;
	int mxson = -1;
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (j != f) {
			dfs1(j, u);
			sz[u] += sz[j];
			if (sz[j] > mxson) {
				mxson = sz[j];
				son[u] = j;
			}
		}
	}
}

void dfs2 (int u, int topf) {
	id[u] = ++ cnt;
	wt[cnt] = w[u];
	top[u] = topf;
	if (!son[u]) return;
	dfs2(son[u], topf);
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (!id[j]) dfs2(j, j);
	}
}

inline void pushup (int u) {
	tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % mod;
}

inline void pushdown (int u) {
	auto &r = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
	if (r.add) {
		right.add = (right.add + r.add) % mod;
		right.sum = (right.sum + (right.r - right.l + 1) * r.add) % mod;
		left.add = (left.add + r.add) % mod;
		left.sum = (left.sum + (left.r - left.l + 1) * r.add) % mod;
		r.add = 0;
	}
}

inline void build (int u, int l, int r) {
	if (l == r) {
		tr[u] = {l, r, wt[r], 0};
		return;
	}
	tr[u] = {l, r, 0, 0};
	int mid = (l + r) >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
	pushup(u);
}

inline void modify (int u, int l, int r, int d) {
	if (tr[u].l >= l && tr[u].r <= r) {
		tr[u].sum = (tr[u].sum + (tr[u].r - tr[u].l + 1) * d) % mod;
		tr[u].add += d;
	}
	else {
		pushdown(u);
		int mid = tr[u].r + tr[u].l >> 1;
		if (l <= mid) modify (u << 1, l, r, d);
		if (r > mid) modify (u << 1 | 1, l, r, d);
		pushup(u);
	}
}

inline int query (int u, int l, int r) {
	if (tr[u].l >= l && tr[u].r <= r) {
		return tr[u].sum;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	int sum = 0;
	if (l <= mid) sum = (sum + query(u << 1, l, r)) % mod;
	if (r > mid) sum = (sum + query(u << 1 | 1, l, r)) % mod;
	return sum;
}

inline void Uprange (int x, int y, int d) {
	while (top[x] != top[y]) {
		if (depth[top[x]] < depth[top[y]]) swap(x, y);
		modify(1, id[top[x]], id[x], d);
		x = fa[top[x]];
	}
	if (depth[x] > depth[y]) swap(x, y);
	modify(1, id[x], id[y], d);
}

inline int Qrange (int x, int y) {
	int sum = 0;
	while (top[x] != top[y]) {
		if (depth[top[x]] < depth[top[y]]) swap(x, y);
		sum = (sum + query(1, id[top[x]], id[x])) % mod;
		x = fa[top[x]];
	}
	if (depth[x] > depth[y]) swap(x, y);
	sum = (sum + query(1, id[x], id[y])) % mod;
	return sum;
}

inline int lca (int x, int y) {
	while (top[x] != top[y]) {
        depth[top[x]] > depth[top[y]] ? x = fa[top[x]] : y = fa[top[y]];
    }
    return depth[x] < depth[y] ? x : y;
}

void solve() {
	scanf("%d%d%d%d", &n, &m, &root, &mod);
	for (int i = 1; i <= n; i ++ ) scanf("%d", w + i);
	ms(h, -1);
	for (int i = 1; i < n; i ++ ) {
		int u, v; scanf("%d%d", &u, &v);
		add(u, v);add(v, u);
	}
	dfs1(root, 0);
	dfs2(root, root);
	build(1, 1, n);
	for (int i = 1; i <= m; i ++ ) {
		int op;
		scanf("%d", &op);
		if (op == 1) {
			int l, r, d;
			scanf("%d%d%d", &l, &r, &d);
			Uprange(l, r, d);
		}
		if (op == 2) {
			int l, r;
			scanf("%d%d", &l, &r);
			printf("%d\n", Qrange(l, r));
		}
		if (op == 3) {
			int x, d;
			scanf("%d%d", &x, &d);
			modify(1, id[x], id[x] + sz[x] - 1, d % mod);
		}
		if (op == 4) {
			int x;
			scanf("%d", &x);
			printf("%d\n", query(1, id[x], id[x] + sz[x] - 1));
		}
	}
}

1.10 Dsu on Tree

// 处理重儿子
void dfs (int u, int fa) {
	sz[u] = 1;
	for (auto j : G[u]) {
		if (j != fa) {
			dfs(j, u);
			sz[u] += sz[j];
			if (sz[j] > sz[son[u]]) son[u] = j;
		}
	}
}

// 这一步需要结合题意
void count (int u, int fa, int v) {
	cnt[val[u]] += v; // 增加或删除贡献
	if (cnt[val[u]] > mx) {
		mx = cnt[val[u]];
		sum = val[u];
	}
	else if (cnt[val[u]] == mx) {
		sum += val[u];
	}
	
    // 统计除标记外的重儿子的所有子树的贡献
	for (auto j : G[u]) {
		if (j == fa || j == flag) continue;
		count(j, u, v);
	}
}

void dfs (int u, int fa, bool op) {
	for (auto j : G[u]) { // 先处理所有轻儿子及其子树并且计算答案,删除贡献。
		if (j != fa && j != son[u]) {
			dfs(j, u, false);
		}
	}
	if (son[u]) { // 处理重儿子及其子树并计算答案,不删除贡献
		dfs(son[u], u, true);
		flag = son[u];
	}
	
	count(u, fa, 1); // 暴力统计u及其所有轻儿子的贡献合并到刚算出的重儿子信息里
	flag = 0;
	ans[u] = sum;
	
    // 把需要删除贡献的删一删
	if (!op) {
		count(u, fa, -1);
		sum = mx = 0; //这是因为count函数中会改变这两个变量值
	}
}

1.11 Splay

#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
#define fa(x) tr[x].fa
#define root tr[0].ch[1]
struct node {
        int fa , ch[2], val, rec, sum, tag;
}tr[maxn];
int tot, pointnum;
void update (int x) {tr[x].sum = tr[ls(x)].sum + tr[rs(x)].sum + tr[x].rec;}
int ident (int x) {return tr[fa(x)].ch[0] == x ? 0 : 1;}
void connect (int x, int fa, int how) {tr[fa].ch[how] = x; tr[x].fa = fa;}


inline void pushdown (int u) {
        if (tr[u].tag) {
                tr[tr[u].ch[0]].tag ^= 1;
                tr[tr[u].ch[1]].tag ^= 1;
                swap(tr[u].ch[0], tr[u].ch[1]);
                tr[u].tag = 0;
        }
}

void rotate (int x) {
        int y = fa(x), r = fa(y);
        int yson = ident(x), rson = ident(y);
        connect(tr[x].ch[yson ^ 1], y, yson);
        connect(y, x, yson ^ 1);
        connect(x, r, rson); 
        update(y), update(x);
}

void splay (int x, int to) {
        to = fa(to);
        while (fa(x) != to) {
                int y = fa(x);
                if (tr[y].fa == to) rotate(x);
                else if (ident(x) == ident(y)) rotate(y), rotate(x);
                else rotate(x), rotate(x);
        }
}

int newnode (int v, int f) {
        tr[ ++ tot].fa = f;
        tr[tot].rec = tr[tot].sum = 1;
        tr[tot].val = v;
        return tot;
}

void insert (int x) {
        int now = root;
        if (!root) {newnode(x, 0); root = tot;}
        else {
                while (1) {
                        tr[now].sum ++ ;
                        if (tr[now].val == x) {tr[now].rec ++ ; splay(now, root); return;}

                        int ne = x < tr[now].val ? 0 : 1;
                        if (!tr[now].ch[ne]) {
                                int p = newnode(x, now);
                                tr[now].ch[ne] = p;
                                splay(p, root); return;
                        }
                        now = tr[now].ch[ne];
                }
        }
}

int find (int x) { // 找到值为x的某一结点
        int now = root;
        while (1) {
                if (!now) return 0;
                if (tr[now].val == x) {splay(now, root); return now;}
                int ne = x < tr[now].val ? 0 : 1;
                now = tr[now].ch[ne];
        }
}

void del (int x) {
        int p = find(x);
        if (!p) return;
        if (tr[p].rec > 1) {tr[p].rec -- , tr[p].sum -- ; return;}
        else {
                if (!tr[p].ch[0] && !tr[p].ch[1]) {root = 0; return;}
                else if (!tr[p].ch[0]) {
                        root = tr[p].ch[1]; tr[root].fa = 0; return;
                }
                else {
                        int left = tr[p].ch[0];
                        while (tr[left].ch[1]) left = tr[left].ch[1];
                        splay(left, tr[p].ch[0]);
                        connect (tr[p].ch[1], left, 1);
                        connect (left, 0, 1);
                        update(left);
                }
        }
}

int rk (int x) { // x的排名
        int now = root, ans = 0;
        while (1) {
                if (tr[now].val == x) return ans + tr[tr[now].ch[0]].sum + 1;
                int ne = x < tr[now].val ? 0 : 1;
                if (ne == 1) ans = ans + tr[tr[now].ch[0]].sum + tr[now].rec;
                now = tr[now].ch[ne];
        }
}

int kth (int x) { //  x排名的数
        int now = root;
        while (1) {
                int used = tr[now].sum - tr[tr[now].ch[1]].sum;
                if (tr[tr[now].ch[0]].sum < x && x <= used) {
                        splay(now, root); return tr[now].val;
                    // return now; 用于区间反转
                }
                if (x < used) now = tr[now].ch[0];
                else now = tr[now].ch[1], x -= used;
        }
}

int pre (int x) {
        int now = root, ans = -inf;
        while (now) {
                if (tr[now].val <= x) ans = max(ans, tr[now].val);
                int ne = x <= tr[now].val ? 0 : 1;
                now = tr[now].ch[ne];
        }
        return ans;
}

int suf (int x) {
        int now = root, ans = inf;
        while (now) {
                if (tr[now].val >= x) ans = min(ans, tr[now].val);
                int ne = x < tr[now].val ? 0 : 1;
                now = tr[now].ch[ne];
        }
        return ans;
}

inline void reverse (int x, int y) { // 区间反转
        int l = kth(x), r = kth(y + 2);
        splay(l, 0); splay(r, l);
        tr[tr[tr[root].ch[1]].ch[0]].tag ^= 1;
}

inline void print (int u) { // 中序遍历
        pushdown(u);
        if (tr[u].ch[0]) print(tr[u].ch[0]);
        if (tr[u].val > 1 && tr[u].val < n + 2) printf("%lld ", tr[u].val - 1);
        if (tr[u].ch[1]) print(tr[u].ch[1]);
}

1.12 莫队

// 普通莫队,add和del可以修改
int sq;
struct query {
	int l, r, id;
	bool operator < (const query &it) const {
		if (l / sq != it.l / sq) return l < it.l;
		if (l / sq & 1) return r < it.r;
		return r > it.r;
	}
}Q[1000010];
int q[1000010], ans[1000010], cnt[2000010], cur, l = 1, r;

inline void add (int x) {
	if (!cnt[q[x]]) cur ++ ;
	cnt[q[x]] ++ ;
}

inline void del (int x) {
	cnt[q[x]] -- ;
	if (!cnt[q[x]]) cur -- ;
}

void solve() {
	read(n);
	sq = sqrt(n);
	for (int i = 1; i <= n; i ++ ) read(q[i]);
	int query; read(query);
	for (int i = 0; i < query; i ++ ) read(Q[i].l), read(Q[i].r), Q[i].id = i;
	sort(Q, Q + query);
	
	for (int i = 0; i < query; i ++ ) {
		while (l > Q[i].l) add( -- l);
		while (r < Q[i].r) add( ++ r );
		while (l < Q[i].l) del(l ++ );
		while (r > Q[i].r) del(r -- );
		ans[Q[i].id] = cur;
	}
	for (int i = 0; i < query; i ++ ) printf("%d\n", ans[i]);
}


//带修莫队,块长可以选择pow(n*t, 0.33333) 或者 pow(n, 0.66666)
const int maxn = 500010;
int sq;
struct query {
	int l, r, t, id;
	bool operator < (const query &it) const {
		if (l / sq != it.l / sq) return l < it.l;
		else if (r / sq != it.r / sq) return r < it.r;
		else return t < it.t;
	}
}Q[maxn];
struct change {
	int p, col;
}c[maxn];
int q[maxn], ans[maxn], cnt[maxn * 2], cur;
int l = 1, r = 0, qcnt, ccnt;

inline void add (int x) {
	if (!cnt[x]) cur ++ ;
	cnt[x] ++ ;
}
inline void del (int x) {
	cnt[x] -- ;
	if (!cnt[x]) cur -- ;
}
inline void work (int x, int ti) {
	if (c[ti].p >= Q[x].l && c[ti].p <= Q[x].r) {
		del(q[c[ti].p]);
		add(c[ti].col);
	}
	swap(q[c[ti].p], c[ti].col);
}

void solve() {
	read(n); read(m);
	sq = pow(n, 0.66666); // 块长
	for (int i = 1; i <= n; i ++ ) {
		read(q[i]);
	}
	char op[10];
	for (int i = 1; i <= m; i ++ ) {
		scanf("%s", op);
		if (op[0] == 'Q') {
			qcnt ++ ;
			read(Q[qcnt].l); read(Q[qcnt].r);
			Q[qcnt].t = ccnt;
			Q[qcnt].id = qcnt;
		}
		else {
			ccnt ++ ;
			read(c[ccnt].p); read(c[ccnt].col);
		}
	}
	sort(Q + 1, Q + qcnt + 1);
	
	int now = 0;
	for (int i = 1; i <= qcnt; i ++ ) {
		while (l > Q[i].l) add(q[ -- l]);
		while (r < Q[i].r) add(q[ ++ r]);
		while (l < Q[i].l) del(q[l ++ ]);
		while (r > Q[i].r) del(q[r -- ]);
		
		while (now < Q[i].t) work(i, ++ now);
		while (now > Q[i].t) work(i, now --);
		
		ans[Q[i].id] = cur;
	}
	
	for (int i = 1; i <= qcnt; i ++ ) printf("%d\n", ans[i]);
}

2. 图论

2.1 最短路

2.1.1 朴素Dijkstra O(n^2)

int g[N][N]; // 邻接矩阵存稠密图
int dist[N]; // 每个点距离点1的距离
bool st[N]; // 判断该点是否确定

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);

    dist[1] = 0;

    for (int i = 0; i < n; i ++ ){ // n个点循环n次

        int t = -1;

        for (int j = 1; j <= n; j ++ ){ // 找未确定的点中dist最小的点
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
            t = j;
        }

        if (t == n && dist[t] != 0x3f3f3f3f) return dist[t];
        else if (t == n && dist[t] == 0x3f3f3f3f) return -1; // 提前结束循环的优化
        
        st[t] = true;

        for (int j = 1; j <= n; j ++ )// 用t更新其他未确定点的距离
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    
    if (dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
}

int main()
{
    cin >> n >> m;

    memset(g, 0x3f, sizeof g);

    while (m -- ){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }

    printf("%d\n", dijkstra());

    return 0;
}

2.1.2 堆优化Dijkstra O(mlogn)

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

const int N = 1e6 + 10;

int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];

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

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1}); // first存储距离,second存储节点编号

    while (heap.size()){
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;
        
        if (st[ver]) continue; // 防止产生冗余
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[ver] + w[i]){
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }

    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m -- ){
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }

    cout << dijkstra() << endl;

    return 0;
}

2.1.3 Bellman-ford O(nm)

// 有边数限制的最短路

struct Edge
{
    int a, b ,c;
}edges[M]; // 结构体存边

int n, m, k;
int dist[N];
int backup[N]; // 题目有特殊的边数限制,因此在更新时只能更新上次备份,否则会出现串联

void bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);

    dist[1] = 0;
    for (int i = 0; i < k; i ++ ){
        memcpy(backup, dist, sizeof dist); // 每次都将上次的dist存到备份里
        for (int j = 0; j < m; j ++ ){
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], backup[e.a] + e.c);
        }
    }
}

int main()
{
    cin >> n >> m >> k;

    for (int i = 0; i < m ; i ++ ){
        int a, b, c;
        cin >> a >> b >> c;
        edges[i] = {a, b, c};
    }

    bellman_ford();

    if (dist[n] > 0x3f3f3f3f / 2) printf("impossible\n");
    else printf("%d\n", dist[n]);

    return 0;
}



// 无边数限制的最短路
#include <bits/stdc++.h>
using namespace std;

const int M = 1000010;

struct Edge
{
    int a, b ,c;
}edges[M]; // 结构体存边

int n, m, k;
int dist[M];


int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);

    dist[1] = 0;
    //// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ ){
        for (int j = 0; j < m; j ++ ){
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], dist[e.a] + e.c);
        }
    }
    
    if (dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

int main()
{
    cin >> n >> m;

    for (int i = 0; i < m ; i ++ ){
        int a, b, c;
        cin >> a >> b >> c;
        edges[i] = {a, b, c};
    }
    
    if (bellman_ford() == -1) printf("impossible\n");
    else printf("%d\n", dist[n]);

    return 0;
}

2.1.4 SPFA O(nm)

// 最短路
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;

int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];

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

int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size()){
        int t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                if (!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    printf("%d\n", dist[n]);
    return dist[n];
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);

    while (m -- ){
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }

    if (spfa() == 0x3f3f3f3f) printf("impossible");
    else printf("%d\n", spfa());

    return 0;
}


// 判断负权环
#include <bits/stdc++.h>
using namespace std;

const int N = 2010, M = 10010;

int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
// 优化:queue改stack,或入队总数大于2*n即有负环
bool spfa()
{
    queue<int> q;

    for (int i = 1; i <= n; i ++ ){
        st[i] = true;
        q.push(i);
    }

    while (q.size()){
        int t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
				
                if (cnt[j] >= n) return true;

                if (!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;
}

int main()
{
    cin >> n >> m;
    memset (h, -1, sizeof h);

    while (m -- ){
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }

    if (spfa()) cout << "Yes" << endl;
    else cout << "No" << endl;

    return 0;
}

// SLF优化
int dist[maxn];
int cnt[maxn];
bool st[maxn];
vector<PII> G[maxn];

bool spfa(int s)
{
   	deque<int> q;
    dist[s] = 0;
    q.push_back(s);
    cnt[s] ++ ;
	st[s] = 1;
    
    while (q.size()) {
		int t = q.front();
        q.pop_front();
        st[t] = 0;
        
        for (auto it : G[t]) {
            int j = it.first, w = it.second;
            if (dist[j] > dist[t] + w) {
                dist[j] = dist[t] + w;
                if (!st[j]) {
                    if (!q.empty() && dist[j] > dist[q.front()]) {
                        q.push_back(j);
                    }
                    else q.push_front(j);
                    cnt[j] ++ ;
                    if (cnt[j] > n) return true;
                    st[j] = 1;
                }
            }
        }
    }
    return false;
}

2.1.5 Floyd O(n^3)

#include <bits/stdc++.h>
using namespace std;

const int N = 210, INF = 1e9;

int n, m, Q;
int d[N][N]; // i到j距离

void floyd()
{
    for (int k = 1; k <= n; k ++ ){
        for (int j = 1; j <= n; j ++ ){
            for (int i = 1; i <= n; i ++ ){
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
}

int main()
{
    cin >> n >> m >> Q;

    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= n; j ++ ){
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;
        }
    }

    while (m -- ){
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = min(d[a][b], c);
    }

    floyd();

    while (Q -- ){
        int a, b;
        cin >> a >> b;
        int t =d[a][b];
        if (t > INF / 2) printf("impossible");
        else printf("%d\n", t);
    } 

    return 0;
}

// 传递闭包
for (int k = 0; k < n; k ++ ) {
    for (int i = 0; i < n; i ++ ) {
        for (int j = 0; j < n; j ++ ) {
            d[i][j] |= d[i][k] && d[k][j];
        }
    }
}


// 求最小环
int n, m;
int d[maxn][maxn], g[maxn][maxn];
int pos[maxn][maxn];
int path[maxn], cnt;

void get_path (int i, int j) {
        if (pos[i][j] == 0) return;
        
        int k = pos[i][j];
        get_path(i, k);
        path[cnt ++ ] = k;
        get_path(k, j);
}

int main () {
        cin >> n >> m;
        
        memset(g, 0x3f, sizeof g);
        for (int i = 1; i <= n; i ++ ) g[i][i] = 0;
        
        while (m -- ) {
                int a, b, c;
                cin >> a >> b >> c;
                g[a][b] = g[b][a] = min(g[a][b], c);
        }
        
        int res = inf;
        memcpy(d, g, sizeof d);
        for (int k = 1; k <= n; k ++ ) {
                for (int i = 1; i < k; i ++ ) {
                        for (int j = i + 1; j < k; j ++ ) {
                                if((long long)d[i][j] + g[j][k] + g[k][i] < res) {
                                        res = d[i][j] + g[j][k] + g[k][i];
                                        cnt = 0;
                                        path[cnt ++] = k;
                                        path[cnt ++] = i;
                                        get_path(i, j);
                                        path[cnt ++] = j;
                                }
                        }
                }
                for (int i = 1; i <= n; i ++ ) {
                        for (int j = 1; j <= n; j ++ ) {
                                if (d[i][j] > d[i][k] + d[k][j]) {
                                        d[i][j] = d[i][k] + d[k][j];
                                        pos[i][j] = k;
                                }
                        }
                }
        }
        
        if (res == inf) puts("No solution.");
        else {
                for (int i = 0; i < cnt; i ++ ) cout << path[i] << ' ';
                cout << endl;
        }
}

2.1.6 路径还原

//以朴素dijkstra为例,记录一个path数组,当dist数组被更新时,就同步跟新path数组

#include <bits/stdc++.h>
using namespace std;

const int maxn = 510;
const int inf = 0x3f3f3f3f;
int g[maxn][maxn];
int st[maxn];
int dist[500010];
int path[500010]; // 记录走来的路径
int n, m;

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    memset(path, -1 ,sizeof path);
    
    dist[1] = 0;
    
    for (int i = 1; i <= n; i ++ ){
        int t = -1;
        for (int j = 1; j <= n; j ++ ){
            if (!st[j] && (t == -1 || dist[j] < dist[t])) t = j;
        }
        
        st[t] = 1;
        
        for (int j = 1; j <= n; j ++ ){
            if (dist[j] > dist[t] + g[t][j]){
                dist[j] = dist[t] + g[t][j];
                path[j] = t; // 记录
            }
        }
    }
    return dist[n];
}

vector<int> get_path(int x){
    vector<int> p;
    for (; x != -1; x = path[x]) p.push_back(x);
    reverse(p.begin(), p.end()); //p中存下的是n到1的顺序,我们逆反一下顺序。
    return p;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= n; j ++ ){
            g[i][j] = (i == j) ? 0 : inf;
        }
    }
    
    for (int i = 1; i <= m; i ++ ){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }
    
    printf("%d\n", dijkstra());
    vector<int> p = get_path(n);
    for (auto it : p){
        printf("%d ", it);
    }
    return 0;
}

2.1.7 最短路计数

int cnt[maxn]; // 长度为i的路径的数量。


while (q.size()) { // 以bfs为例
                auto t = q.front();
                q.pop();
                
                for (auto it : G[t]) {
                        if (dist[it] > dist[t] + 1) {
                                dist[it] = dist[t] + 1;
                                cnt[it] = cnt[t];
                                q.push(it);
                        }
                        else if (dist[it] == dist[t] + 1) cnt[it] = (cnt[it] + cnt[t]) % mod;
                }
        }

2.2 最小生成树

2.2.1 Prim

#include <bits/stdc++.h>
using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N]; // 邻接矩阵存图
int dist[N]; // 1~i的距离
bool st[N];

int prim()
{
    memset(dist, 0x3f, sizeof dist);

    int res = 0;
    for (int i = 0; i < n; i ++ ){
        int t = -1;

        for (int j = 1; j <= n; j ++ ){
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
            t = j;
        }

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

int main()
{
    cin >> n >> m;

    memset(g, 0x3f, sizeof g);

    while (m -- ){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c); // 无向图
    }

    int t = prim();

    if (t == INF) printf("impossible");
    else printf("%d\n", t);

    return 0;
}

2.2.2 Kruskal

#include <bits/stdc++.h>
using namespace std;

const int N = 100010, M = 200010, INF = 0x3f3f3f3f;

int n, m;
int p[N]; // 并查集

struct Edge
{
    int a, b, w;
}edges[M];

bool cmp(Edge a, Edge b)
{
    return a.w < b.w;
}

int find (int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m, cmp);

    for (int i = 0; i <= n; i ++ ) p[i] = i; // 并查集的初始操作

    int res = 0, cnt = 0; // cnt表示连通的边数
    for (int i = 0; i < m; i ++ ){
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b){
            p[a] = b; // 将边加入集合
            res += w;
            cnt ++ ;
        }
    }
    if (cnt < n - 1) return INF;
    return res;
}

int main()
{
    cin >> n>> m;

    for (int i = 0; i < m; i ++ ){
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w};
    }

    int t = kruskal();

    if (t == INF) printf("impossible"); // 不连通
    else printf("%d", t);

    return 0;
}

// 次小生成树
#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 510, maxm = 10010;
typedef pair<int, int> PII;
int n, m;
struct node {
        int a, b, w;
        bool f;
        bool operator < (const node &it) const {
                return w < it.w;
        }
}e[maxm];
int p[maxn];
int d1[maxn][maxn], d2[maxn][maxn];
vector<PII> G[maxn];

int find (int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
}

void dfs (int u, int fa, int maxd1, int maxd2, int d1[], int d2[]) {
        d1[u] = maxd1, d2[u] = maxd2;
        for (auto it : G[u]) {
                int j = it.first, w = it.second;
                if (j != fa) {
                        int td1 = maxd1, td2 = maxd2;
                        if (w > td1) td2 = td1, td1 = w;
                        else if (w < td1 && w > td2) td2 = w;
                        dfs(j, u, td1, td2, d1, d2);
                }
        }
}

signed main () {
        cin >> n >> m;
        for (int i = 1; i <= m; i ++ ) {
                int a, b, w;
                cin >> a >> b >> w;
                e[i] = {a, b, w};
        }
        
        sort(e + 1, e + m + 1);
        for (int i = 1; i <= n; i ++ ) p[i] = i;
        
        int sum = 0;
        for (int i = 1; i <= m; i ++ ) {
                int a = e[i].a, b = e[i].b, w = e[i].w;
                int pa = find(a), pb = find(b);
                if (pa != pb) {
                        p[pa] = pb;
                        sum += w;
                        G[a].push_back({b, w});
                        G[b].push_back({a, w});
                        e[i].f = 1;
                }
        }
        
        for (int i = 1; i <= n; i ++ ) {
                dfs(i, -1, -1e9, -1e9, d1[i], d2[i]);
        }
        
        int res = 1e18;
        for (int i = 1; i <= m; i ++ ) {
                if (!e[i].f) {
                        int a = e[i].a, b = e[i].b, w = e[i].w;
                        int t;
                        if (w > d1[a][b]) {
                                t = sum + w - d1[a][b];
                        }
                        else if (w > d2[a][b]) {
                                t = sum + w - d2[a][b];
                        }
                        res = min(res, t);
                }
        }
        cout << res << endl;
}

2.3 二分图匹配

2.3.1 染色法判断二分图

/*
染色法的实现思路(DFS):
1.用1,2代表两个颜色,0代表未染色,任选一个点染成1或2

2.遍历所有点,每次将未染色的点进行dfs

3.若染色失败即break/return
*/  
  
#include <bits/stdc++.h>
using namespace std;

const int N = 100010, M = 200010;

int n, m;
int h[N], e[M], ne[M], idx;
int color[N];

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

bool dfs(int u, int c)
{
    color[u] = c; // 染色
    for (int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if (!color[j]){
            if (!dfs(j, 3 - c)) return false; // 如果在dfs递归的过程中出现染色失败,则整个图都不是二分图
        }
        else if (color[j] == c) return false; // 如果一条边的两端点同种颜色,则染色失败
    }

    return true; // 无染色错误则染色成功
}

int main()
{
    cin >> n >> m;

    memset(h, -1, sizeof h);

    while (m -- ){
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }

    bool flag = true;
    for (int i = 1; i <= n; i ++ ){ // 遍历所有点,因为二分图不一定是连通图
        if (!color[i]){
            if (!dfs(i, 1)){
                flag = false;
                break;
            }
        }
    }
    if (flag) printf("Yes\n");
    else printf("No");

    return 0;
}

2.3.2 匈牙利算法判断最大匹配

#include <bits/stdc++.h>
using namespace std;

const int N = 510, M = 100010;

int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N]; // match[a] = b, 表示点a目前匹配了b
bool st[N];  // st[a] = true, 表示点a目前已经有预定

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

bool find(int x) // 注意与并查集的find函数区别,为x找一个匹配,或x的匹配点被别人预定,x要重新找一个匹配
{
    // 匹配成功返回true
    for (int i = h[x]; i != -1; i = ne[i]){
        int j = e[i];
        if (!st[j]){ // 该点目前没有被匹配
            st[j] = true; // 预定该点
            if (match[j] == 0 || find(match[j])){ // j点没有匹配,或与j匹配的点可以更换匹配
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    scanf("%d%d%d", &n1, &n2, &m);

    memset(h, -1, sizeof h);

    while (m -- ){
        int a, b;
        cin >> a >> b;
        add(a, b); // 从左集合找右集合,只存一条边也可以
    }

    int res = 0;
    for (int i = 0; i <= n1; i ++ ){ // 二分图不一定连通,因此要为所有点尝试匹配	
        memset(st, false, sizeof st);
        if (find(i)) res ++;
    }
    cout << res << endl;

    return 0;
}

最小点覆盖 = 最大匹配
最大独立集 = n - 最大匹配
最小路径点覆盖= n - 最大匹配
    

2.4 拓扑排序

const int N = 1e5 + 10;
int e[N],ne[N],h[N],idx;
int d[N];// d 代表每个元素的入度
int top[N];// top是拓扑排序的序列
int cnt = 1; // cnt代表top中有多少个元素
int n, m;
void add(int a,int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}
bool topsort(){
    queue<int> q;
    int t;
    for(int i = 1;i <= n; ++i)// 将所有入度为0的点加入队列
        if(d[i] == 0) q.push(i);
    while(q.size()){
        t = q.front();//每次取出队列的首部
        top[cnt] = t;//加入到 拓扑序列中
        cnt ++; // 序列中的元素 ++
        q.pop();
        for(int i = h[t];i != -1; i = ne[i]){
            // 遍历 t 点的出边
            int j = e[i];
            d[j] --;// j 的入度 --
            if(d[j] == 0) q.push(j); //如果 j 入度为0,加入队列当中
        }
    }
    if(cnt < n) return 0;
    else return 1;

}
int main(){
    int a,b;
    cin >> n >> m;
    memset(h,-1,sizeof h);
    while(m--){
        cin >> a >> b;
        add(a,b);
        d[b] ++;// a -> b , b的入度++
    }
    if(topsort() == 0) cout << "-1"; // 序列不合法
    else {
        for(int i = 1;i <= n; ++i){
            cout << top[i] <<" ";
        }
    }
    return 0;
}

2.5 LCA

int n, m;
int h[maxn], e[maxm], ne[maxm], idx;
int depth[maxn], fa[maxn][20];

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

void bfs (int root) {
        queue<int> q;
        q.push(root);
        memset(depth, 0x3f, sizeof depth);
        depth[0] = 0, depth[root] = 1;
        
        while (q.size()) {
                int t = q.front();
                q.pop();
                
                for (int i = h[t]; ~i; i = ne[i]) {
                        int j = e[i];
                        if (depth[j] > depth[t] + 1) {
                                depth[j] = depth[t] + 1;
                                q.push(j);
                                fa[j][0] = t;
                                for (int k = 1; k <= 19; k ++ ) {
                                        fa[j][k] = fa[fa[j][k - 1]][k - 1];
                                }
                        }
                }
        }
}

int lca (int a, int b) {
        if (depth[a] < depth[b]) swap(a, b);
        for (int k = 19; k >= 0; k -- ) {
                if (depth[fa[a][k]] >= depth[b]) {
                        a = fa[a][k];
                }
        }
        if (a == b) return a;
        for (int k = 19; k >= 0; k -- ) {
                if (fa[a][k] != fa[b][k]) {
                        a = fa[a][k];
                        b = fa[b][k];
                }
        }
        return fa[a][0];
}

2.6 Tarjan 强连通分量

int n, m;
int h[maxn], e[maxm], ne[maxm], idx; // 存图
int dfn[maxn], low[maxn], timestamp; // dfn表示编号为i的点的时间戳,low表示以点i出发走到的最小时间戳,timestamp为当前时间戳
int stk[maxn], top; // 模拟栈
bool in_stk[maxn]; // 判断是否在栈内
int id[maxn], scc_cnt, sz[maxn]; // id为点i在哪个强连通分量,scc_cnt教师强连通分量的数量
int dout[maxn]; // 出度

void tarjan(int u) {
        dfn[u] = low[u] = ++ timestamp;
        stk[ ++ top] = u, in_stk[u] = true;
        for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                if (!dfn[j]) {
                        tarjan(j);
                        low[u] = min(low[u], low[j]);
                }
                else if (in_stk[j]) low[u] = min(low[u], low[j]);
        }
        
        if (dfn[u] == low[u]) {
                ++ scc_cnt;
                int y;
                do {
                        y = stk[top -- ];
                        in_stk[y] = false;
                        id[y] = scc_cnt;
                } while (y != u);
        }
}
int main () {
        cin >> n >> m;
        memset(h, -1, sizeof h);
        while (m -- ) {
                int a, b;
                cin >> a >> b;
                add(a, b);
        }
        
        for (int i = 1; i <= n; i ++ ) {
                if (!dfn[i]) {
                        tarjan(i);
                }
        }
        
        for (int i = 1; i <= n; i ++ ) {
                for (int j = h[i]; ~j; j = ne[j]) {
                        int k = e[j];
                        int a = id[i], b = id[k];
                        if (a != b) dout[a] ++ ;
                }
        }
        
        //剩下由题
}

2.7 差分约束

/* 求最小值,用最长路,求最大值,用最短路。
A = B  <=> A≥B B≥A add(b, a, 0), add(a, b, 0);
A < B  <=> B≥A+1 add(a, b, 1);
A≥B    <=> A≥B add (b, a, 0);
A > B  <=> A≥B+1 add(b, a, 1);
B≥A    <=> B≥A add(a, b, 0);

x >= 1
建立虚拟源点x0 (dist[x0] = 0)
即x >= x0 + 1 add(x0, x, 1);
*/

3 数学

3.1 线性筛

int primes[N], cnt;		// primes[]存储所有素数
bool st[N];			// st[x]存储x是否被筛掉

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

3.2 快速乘 & 快速幂

// 快速幂
int qmi (int a, int b) { // a ^ b
	int ans = 1;
	while (b) {
		if (b & 1) ans = ans * a % mod; // 或 ans = mul(ans, a);
		a = a * a % mod; // 或 ans = mul(a, a);
		b >>= 1;
	}
	return ans;
}


// 快速乘
int mul (int a, int b) { // a * b
	int ans = 0;
    while (b) {
		if (b & 1) ans = (ans + a) % mod;
        b >>= 1;
        a = (a * 2) % mod;
    }
    return ans;
}

3.3 拓展欧几里得算法 exgcd

/ 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
    if (!b) {
        x = 1; y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a/b) * x;
    return d;
}

3.4 欧拉函数

// 求1~N中与N互质的个数
int phi(int x)
{
    int res = x;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }
    if (x > 1) res = res / x * (x - 1);
    
    return res;
}

// 筛法求欧拉函数
int primes[N], cnt;		// primes[]存储所有素数
int euler[N];			// 存储每个数的欧拉函数
bool st[N];			// st[x]存储x是否被筛掉


void get_eulers(int n)
{
    euler[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)
            {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

3.5 约数 个数/和 定理

// 约数个数
//n=p1^a1*p2^a2*p3^a3*…*pk^ak
//f(n) = (a1+1)(a2+1)(a3+1)…(ak+1)
int main()
{
    int n;
    cin >> n;

    unordered_map<int, int> primes;

    while (n -- )
    {
        int x;
        cin >> x;

        for (int i = 2; i <= x / i; i ++ )
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }

        if (x > 1) primes[x] ++ ;
    }

    LL res = 1;
    for (auto p : primes) res = res * (p.second + 1) % mod;

    cout << res << endl;

    return 0;
}

// 约数之和
//n=p1^a1*p2^a2*p3^a3*…*pk^ak
//f(n)=(p1^0+p1^1+p1^2+…p1^a1)(p2^0+p2^1+p2^2+…p2^a2)…(pk^0+pk^1+pk^2+…pk^ak)
unordered_map<int, int> mp;
    while(t -- )
    {
        int x; scanf("%d", &x); // 即n
        for(int i = 2; i <= x / i; i ++)
        {
            while(x % i == 0)
            {
                x /= i;
                mp[i]++;
            }
        }
        if(x > 1) mp[x] ++;
    }
    long long res = 1;
    for(auto p : mp)
    {
        long long a = p.first, b = p.second;
        long long t = 1;
        while(b -- )
        {
            t = (t * a + 1) % mod; // 秦九韶算法
        }
        res = res * t % mod;
    }
    cout << res << endl;

3.6 组合数

// O(ab)
const int N = 2010;
int c[N][N];

void init () {
    for (int i = 0; i < N ; i ++ ) {
		for (int j = 0; j <= i; j ++ ) {
			if (!j) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
        }
    }
}


// lucas定理
// C(a, b) = C(a % p, b % p) * C(a / p, b / p); p为质数
int a, b, p; // 求C(a, b) % p;
int qmi(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}


int C(int a, int b, int p)
{
    if (b > a) return 0;

    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (LL)res * j % p;
        res = (LL)res * qmi(i, p - 2, p) % p;
    }
    return res;
}


int lucas(LL a, LL b, int p)
{
    if (a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}






inline void init () {
	fac[0] = 1;
	for (int i = 1; i <= n; i ++ ) fac[i] = fac[i - 1] * i % mod2;
	ifac[n] = qmi(fac[n], mod2 - 2) % mod2;
	for (int i = n; i >= 1; i -- ) ifac[i - 1] = ifac[i] * i % mod2;
}

inline int C (int n, int m) {
	return fac[n] * ifac[m] % mod2 * ifac[n - m] % mod2;
}

// k次前缀和的组合数
C(k-1+i,k-1

3.7 整除分块

$$
\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor
$$

// 下取整
inline int getdown (int n) {
	int ans = 0;
    for(int l = 1, r, len; l <= n; l = r + 1) {
        r = n / (n / l), len = r - l + 1;
        ans += len * (n / l);
    }
    return ans;
}

// 上取整
inline int getup (int n) {
	int ans = 0;
    for(int l = 1, r, len; l <= n; l = r + 1) {
        r = n / (n / l), len = r - l + 1;
        ans += len * (n / l + 1);
        if (n % r == 0) ans -= r;
    }
    return ans;
}

3.8 卡特兰数

$$
f(n)=C_{2n}^{n}-C_{2n}^{n-1}
$$

1.n 个元素进栈序列为:1,2,3,4,...,n,则有多少种出栈序列。
2.n 对括号,则有多少种 “括号匹配” 的括号序列
3.n + 1 个叶子节点能够构成多少种形状不同的(国际)满二叉树
4.电影票一张 50 coin,且售票厅没有 coin,m 个人各自持有 50 coin,n 个人各自持有 100 coin。则有多少种排队方式,可以让每个人都买到电影票。
5.8 个高矮不同的人需要排成两队,每队 4 个人。其中,每排都是从低到高排列,且第二排的第 i 个人比第一排中第 i 个人高,则有多少种排队方式?
6.在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形。任务是键盘上输入凸多边形的边数n,求不同划分的方案数f(n)。

前几项:
1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, ...

3.9 线性求逆

ll inv[maxn] = {0,1};
for (int i = 2; i < maxn; i ++ ) 
    inv[i] = (mod - mod / i) * inv[mod % i] % mod;

3.10 博弈论

经典nim
n堆,每次拿任意个,不可不拿,先拿完赢
// 先手是否必胜?
cin >> n;
int res = 0;
for (int i = 0; i < n; i ++ ){
    int a;
    cin >> a;
    res ^= a;
}

if (res) printf("Yes\n");
else printf("No\n");


anti-nim游戏
n堆,每次拿任意个,不可不拿,先拿完输
先手胜当且仅当 ①所有堆石子数都为1且游戏的SG值为0(即有偶数个孤单堆-每堆只有1个石子数);②存在某堆石子数大于1且游戏的SG值不为0.
 bool ok = 0;
int ans = 0;
for (int i = 0; i < sz(res); i ++ ) {
    ans ^= res[i];
    if (res[i] > 1) ok = 1;
}
     
if ((!ok && ans == 0) || (ok && ans != 0)) puts("Alice win");
else puts("Bob win");


对称博弈

3.11 快速傅里叶变换

struct Complex{
    double x,y;
    Complex (double x = 0, double y = 0) : x(x), y(y) {}

    inline Complex operator + (const Complex b) const {
        return Complex (x + b.x, y + b.y);
    }

    inline Complex operator - (const Complex b) const {
        return Complex(x - b.x, y - b.y);
    }

    inline Complex operator * (const Complex b) const {
        Complex res;
        res.x = x * b.x - y * b.y;
        res.y = x * b.y + y * b.x;
        return res;
    }
}a[maxn], b[maxn];
int n, m, k;
int rev[maxn], l;
int lim = 1;

void FFT (Complex *a, int type) {
	for (int i = 0; i < lim; i ++ ) {
		if (i < rev[i]) swap(a[i], a[rev[i]]);
	}
	for (int mid = 1; mid < lim; mid <<= 1) { // 区间的中点
		Complex wn(cos(pi / mid), type * sin(pi / mid));
		for (int r = mid << 1, j = 0; j < lim; j += r) { // 右端点
			Complex w(1, 0);
			for(int k = 0; k < mid; k ++ , w = w * wn) { // 枚举左半区间
				Complex x = a[j + k], y = w * a[mid + j + k];
				a[j + k] = x + y, a[mid + j + k] = x - y;
			}
		}
	}
}

void solve() {
	scanf("%d%d", &n, &m);
	for (int i = 0; i <= n; i ++ ) scanf("%lf", &a[i].x);
	for (int i = 0; i <= m; i ++ ) scanf("%lf", &b[i].x);
	
	while (lim <= n + m) lim <<= 1, l ++ ;
	for (int i = 0; i < lim; i ++ ) {
		rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	}
	FFT(a, 1), FFT(b, 1);
	for (int i = 0; i <= lim; i ++ ) a[i] = a[i] * b[i];
	FFT(a, -1);
	for (int i = 0; i <= n + m; i ++ ) printf("%d ", (int)(a[i].x / lim + 0.5));
}




//大整数相乘
cin >> A >> B;
	for (int i = A.size() - 1; i >= 0; i -- ) {
		a[A.size() - i - 1].x = A[i] - '0';
	}
	for (int i = B.size() - 1; i >= 0; i -- ) {
		b[B.size() - i - 1].x = B[i] - '0';
	}
	while (lim <= A.size() + B.size()) lim <<= 1, l ++ ;
	for (int i = 0; i < lim; i ++ ) {
		rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
	}
	FFT(a, 1), FFT(b, 1);
	for (int i = 0; i <= lim; i ++ ) {
		a[i] = a[i] * b[i];
	}
	FFT(a, -1);
	for (int i = 0; i <= lim; i ++ ) {
		ans[i] += (int)(a[i].x / lim + 0.5);
		if (ans[i] >= 10) {
			ans[i + 1] += ans[i] / 10;
			ans[i] %= 10;
			if (i == lim) lim ++ ;	
		}
	}
	
	while (!ans[lim] && lim >= 1) lim -- ;
	while (lim >= 0) {
		printf("%d", ans[lim -- ]);
	}

3.12 最小二乘法求线性回归方程

$$
k = \frac{\sum_{i = 1}^{n} x_iy_i - n\bar{x}\bar{y}}{\sum_{i = 1}^{n} x_i^2 - n\bar{x}^2}
$$

$$
d = \bar{y} - k\bar{x}
$$

for (int i = 1; i <= n; i ++ ) q[i] = read();
	double sumx = 0, sumy = 0, iq = 0, ii = 0;
	for (int i = 1; i <= n; i ++ ) {
		sumx += i, sumy += q[i];
		iq += i * q[i];
		ii += i * i;
	}
	double k = (iq - sumx * sumy / n) / (ii - sumx * sumx / n);
	double d = sumy / n - k * sumx / n;
	
	double ans = 0;
	for (int i = 1; i <= n; i ++ ) {
		ans += (k * i + d - q[i]) * (k * i + d - q[i]);
	}
	printf("%.6Lf\n", ans);

3.13 线性基

const int MAXL = 60;
struct LinearBasis {
        int a[MAXL + 1];
        int cnt = 0;
        
        LinearBasis () {
                fill(a, a + MAXL + 1, 0);
        }

        LinearBasis (int *x, int n) { // 快速构造线性基
                build (x, n);
        }

        inline bool insert (int t) { // 线性基动态插入数
                for (int j = MAXL; j >= 0; j -- ) {
                        if (!t) return false;
                        if (!(t & (1ll << j))) continue;

                        if (a[j]) t ^= a[j];
                        else {
                                cnt ++ ;
                                // for (int k = 0; k < j; k ++ ) {
                                //         if (t & (1ll << k)) t ^= a[k];
                                // }
                                // for (int k = j + 1; k <= MAXL; k ++ ) {
                                //         if (a[k] & (1ll << j)) a[k] ^= t;
                                // }
                                a[j] = t;
                                return false;
                        }
                }
                return true;
        }

        void build (int *x, int n) {
                fill(a, a + MAXL + 1, 0);
                for (int i = 1; i <= n; i ++ ) insert(x[i]);
        }
        inline void rebuild () { // 重构成易于求kth的形式
                for (int i = 0; i <= MAXL; i ++ ) {
                        for (int j = i - 1; j >= 0; j -- ) {
                                if (a[i] & (1ll << j)) a[i] ^= a[j];
                        }
                }
        }

        inline void mergefrom (const LinearBasis &b) { // 合并两个线性基
                for (int i = 0; i <= MAXL; i ++ ) insert(b.a[i]);
        }

        static LinearBasis merge (const LinearBasis &a, const LinearBasis &b) {
                LinearBasis res = a;
                for (int i = 0; i <= MAXL; i ++ ) res.insert(b.a[i]);
                return res;
        }

        inline int querymax () { // 查询子集最大异或和
                int res = 0;
                for (int i = 0; i <= MAXL; i ++ ) res ^= a[i];
                return res;
        }
        inline int querymin () { // 查询子集最小异或和
                for (int i = 0; i <= MAXL; i ++ ) if (a[i]) return a[i];
        }

        inline int kth (int k) { // k大异或和
                if (cnt < n) k -- ;
                if (k >= (1ll << cnt)) return -1;

                int ans = 0;
                for (int i = 0, j = 0; i <= MAXL; i ++ ) {
                        if (a[i]) {
                                if (k & (1ll << j)) ans ^= a[i];
                                j ++ ;
                        }
                }
                return ans;
        }

        inline int rank (int x) { // 查询异或和排名,不去重需要 k % mod * qmi(2, n - a.cnt) % mod + 1) % mod;
                int ans = 0;
                for (int i = 0, j = 0; i <= MAXL; i ++ ) {
                        if (a[i]) {
                                if (x & (1ll << i)) ans |= (1ll << j);
                                j ++ ;
                        }
                }
                return ans;
        }
};

4 动态规划

4.1 线性DP

4.1.2 最长上升子序列 O(nlogn)

// O(n^2)
int f[N], q[N]; // q原数组,f[i]表示以结尾的最长上升序列
int mx = 1;

for (int i = 1; i <= n; i ++ ) {
	f[i] = 1; // 设f[i]默认为1,找不到前面数字小于自己的时候就为1
    for (int j = 1; j < i; j ++ ) {
		if (q[i] > q[j]) f[i] = max(f[i], f[j] + 1);
    }
    mx = max(mx, f[i]);
}

// O(nlogn)
vector<int> stk;
stk.push_back(q[1]);
for (int i = 2; i <= n; i ++ ) {
	if (q[i] > stk.back()) stk.push_back(q[i]); // //如果该元素大于栈顶元素,将该元素入栈
    else *lower_bound(stk.begin(), stk.end(), q[i]) = q[i]; // //替换掉第一个大于或者等于这个数字的那个数
}
cout << stk.size() << endl;

4.1.3 最长公共子序列 O(n^2)

int a[N], b[N];
int f[N][N]; // f[i][j]表示a串前i个元素和b串前j个元素的最长公共子序列长度,注意下标从1开始

cin >> n >> m >> a + 1 >> b + 1;
for (int i = 1; i <= n; i ++ ) {
    for (int j = 1; j <= m; j ++ ) {
        if (a[i] == b[j]) {
            f[i][j] = f[i - 1][j - 1] + 1;
        }
        else {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);
        }
    }
}

// LCS + 输出字符串
cin >> n >> m >> a + 1 >> b + 1;
int A = strlen(a + 1), B = strlen(b + 1); // a串b串长度
for (int i = A; i >= 1; i -- ) {
    for (int j = B; j >= 1; j -- ) {
        if (a[i] == b[j]) f[i][j] = f[i + 1][j + 1] + 1;
        else f[i][j] = max (f[i + 1][j], f[i][j + 1]);
    }
}

int i = 1, j = 1;
while (i <= A && j <= B) {
    if (a[i] == b[j]) {
        cout << a[i]; // 输出最长公共子串
        i ++ , j ++ ;
    }
    else if (f[i][j] == f[i + 1][j]) i ++ ;
    else j ++ ;
}

4.1.4 最长公共上升子序列 O(n^2)

#include <bits/stdc++.h>
using namespace std;

const int maxn = 3010;
int a[maxn], b[maxn];
int f[maxn][maxn]; // f[i] [j] 代表所有a[1 ~ i]和b[1 ~ j]中以b[j]结尾的公共上升子序列的长度最大值;
int n;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) cin >> b[i];

    for (int i = 1; i <= n; i ++ ){
        int maxv = 1;
        for (int j = 1; j <= n; j ++ ){
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
            if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
        }
    }
    
    int ans = 0;
    for (int i = 1; i <= n; i ++ ) ans = max(ans, f[n][i]);
    cout << ans << endl;
}

4.2 背包

4.2.1 01背包 O(nm)

// 求恰好装满的最优解,f[0] = 0, f[1~m] = -inf;

int n, m;
int v[N], w[N];
int f[N];

int main()
{
    cin >> n >> m; // n件物品,背包容量为m

    for (int i = 1; i <= n; i ++ ){
        int v, w;
        cin >> v >> w; // v质量,w价值
        for (int j = m; j >= v; j -- ) f[j] = max(f[j], f[j - v] + w);
	}
    
    cout << f[m] << endl;

    return 0;
}

4.2.2 完全背包 O(nm)

// 每件物品有无限个
int f[N];

int main()
{
    cin >> n >> m; // n件物品,背包容量为m
    for (int i = 0; i <= n; i ++ ){
        int v, w;
        cin >> v >> w;// v质量,w价值
        // 正序枚举
        for (int j = v; j <= m; j ++ ) f[j] = max(f[j], f[j - v] + w); 
    }

    cout << f[m] << endl;
    return 0;
}

4.2.3 多重背包

4.2.3.1 二进制优化 O (NVlogs)

 // 每件物品最多有Si个,用二进制优化后再做01背包
int v[N], w[N];
int f[M];

int main()
{
    cin >> n >> m;

    int cnt = 0; // 表示下标
    for (int i = 1; i <= n; i ++ ){
        int a, b, s; // a质量,b价值,s最多个数
        cin >> a >> b >> s;
        int k = 1; // 幂次
        while (k <= s){
            cnt ++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }

        if (s > 0){  // 表示有大于幂次的部分
            cnt ++ ;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }

    //n = cnt;

    for (int i = 1; i <= cnt; i ++ ){
        for (int j = m; j >= v[i]; j -- ){
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

    cout << f[m] << endl;

    return 0;
}

4.2.3.2 单调队列优化 O (NV)

int f[N], g[N], q[N]; // fj为前i个物品,在体积j下的最大价值,g为前i-1个物品,在体积j下的最大价值, q为单调队列.

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ )
    {
        int v, w, s;
        cin >> v >> w >> s;
        memcpy(g, f, sizeof f);
        for (int j = 0; j < v; j ++ )
        {
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v)
            {
                if (hh <= tt && q[hh] < k - s * v) hh ++ ;
                while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;
                q[ ++ tt] = k;
                f[k] = g[q[hh]] + (k - q[hh]) / v * w;
            }
        }
    }

    cout << f[m] << endl;

    return 0;
}

4.2.4 分组背包 O (nms)

int v[N][N], w[N][N], s[N];
int f[N];

int main()
{
    cin >> n >> m;

    for (int i = 1; i <= n; i ++ ){
        cin >> s[i];
        for (int j = 0; j < s[i]; j ++ ){
            cin >> v[i][j] >> w[i][j];
        }
    }

    for (int i = 1; i <= n; i ++ ){
        for (int j = m; j >= 0; j -- ){
            for (int k = 0; k < s[i]; k ++ ){
                if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
            }
        }
    }

    cout << f[m] << endl;

    return 0;
}

4.2.5 超大背包

// 当物品质量过高,则将价值和质量转换
int f[maxn]; // f[i]为价值为i下的最小体积
void solve()
{
    cin >> n >> m;
    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    for (int i = 1; i <= n; i ++ ){
        int w, v;
        cin >> v >> w;
        // 用价值上限暴力枚举
        for (int j = 100000; j >= w; j -- ) f[j] = min(f[j], f[j - w] + v);
    }
    int ans = 0;
    for (int i = 1; i <= 100000; i ++ ){
        if (f[i] <= m) ans = max(ans, i); // 暴力枚举所有价值,当体积能装下即为一组合法解
    }
    cout << ans << endl;
}

4.2.6 二维费用背包(有物品数量限制的背包) O(nmk)

int n, V, M;
int f[maxn][maxn]; // 表示不超过i的第一费用,j的第二费用的最大价值。

int main()
{
    cin >> n >> V >> M; // M为第二重最大费用,或为物品数量限制
    for (int i = 1; i <= n; i ++ ){
        int v, m, w;
        cin >> v >> m >> w;
        for (int j = V; j >= v; j -- ){
            for (int k = M; k >= m; k -- ){ // 第二重费用,或为物品数量限制
                f[j][k] = max(f[j][k], f[j - v][k - m] + w);
            }
        }
    }
    cout << f[V][M] << endl;
}

4.2.7 混合背包

// 01背包,完全背包,多重背包的混合
// 考虑将01背包和完全背包转化为多重背包,再使用二进制优化求解多重背包
int n, m, v[100010], w[100010], dp[100010];

int main()
{
    cin >> n >> m; // n件物品,容量为m
    int cnt = 1;
    for(int i = 1; i <= n; i ++)
    {
        int a, b, s;
        cin >> a >> b >> s; //a体积,b价值
        int k = 1; // 幂次
        if(s < 0) s = 1; // s<0,01背包,只能拿一个
        else if(s == 0) s = m / a; // s=0,完全背包,最多拿m/a个
        while(k <= s)
        {
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
            cnt ++;
        }
        if(s > 0)
        {
            v[cnt] = s * a;
            w[cnt] = s * b;
            cnt ++ ;
        }
    }
    for(int i = 1; i <= cnt; i ++)
    {
        for(int j = m; j >= v[i]; j -- )
        {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[m];
}

4.2.6 背包问题输出方案

//用res[i]表示背包容量为i时上次选择了第几个物品.

#include <bits/stdc++.h>
using namespace std;

const int maxn = 101000;
int n, m;
int dp[maxn], res[maxn];
int v[maxn], w[maxn];

int main () {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i ++ ) scanf("%d%d", &v[i], &w[i]);
        
        for (int i = 1; i <= n; i ++ ) {
                for (int j = v[i]; j <= m; j ++ ) {
                        if (dp[i - 1][j] < dp[i - 1][j - v[i]] + w[i]) {
                                res[j] = v[i];
                                dp[j] = dp[j - v[i]] + w[i];
                        }
                }
        }
        
        printf("%d\n", dp[m]);
        
        vector<int> ans;
    	int now = dp[m];	
	    while (res[now] != 0) {
            ans.push_back(res[now]);
            now -= res[now];
        }
    
        for (int i = ans.size() - 1; i >= 0; i -- ) {
            printf("%lld ", ans[i]);
        }
       
}

4.3 区间DP O(n^3)

int f[maxn][maxn], g[maxn][maxn]; // 合并i~j所需的最小/最大价值
int s[maxn]; // 原数组的前缀和
int w[maxn];

for (int i = 1; i <= n; i ++ ) {
        cin >> w[i];
        w[i + n] = w[i]; // 若有环形要求
    }
    
for (int i = 1; i <= n * 2; i ++ ) s[i] = s[i - 1] + w[i];

memset(f, 0x3f, sizeof f);
memset(g, -0x3f, sizeof g);

for (int len = 1; len <= n; len ++ ) { // 枚举区间长度
    for (int l = 1; l + len - 1 <= n * 2; l ++ ) { // 枚举左端点 (若无环形则到n)
        int r = l + len - 1; // 找到右端点
        if (l == r) f[l][r] = g[l][r] = 0;
        else {
            for (int k = l; k < r; k ++ ) { // 枚举分界点
                f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); // minvalue
                g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]); // maxvalue
            }
        }
    }
}

int mi = inf, mx = -inf;
for (int i = 1; i <= n; i ++ ) {
    // 若无环形则直接f[1][n];
    mi = min(mi, f[i][i + n - 1]); 
    mx = max(mx, g[i][i + n - 1]);
}
cout << mi << endl;
cout << mx << endl;

/*
将ax和ax+1合并为ax*ax+1,获得(ax-ax+1)^2
注:不用考虑合并的和得到的分数计算方式不相等,合并完后就是改变ax和ax+1的值,直接照题目计算即可
*/
void solve() {

	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) {
		s[i][i] = a[i];
		for (int j = i + 1; j <= n; j++) {
			s[i][j] = s[i][j - 1] * a[j] % mod;
		}
	}

	for (int len = 2; len <= n; len++) {
		for (int l = 1; l <= n - len + 1; l++) {
			int r = l + len - 1;
			for (int k = l; k < r; k++) {
				dp[l][r] = max(dp[l][r], dp[l][k] + dp[k + 1][r] + (s[l][k] - s[k + 1][r]) * (s[l][k] - s[k + 1][r]));
			}
		}
	}
	cout << dp[1][n] << endl;


}

4.4 树形DP

// 1.树中最长路径,求距离型
int dfs(int u, int f) {
    int dist = 0;
    int d1 = 0, d2 = 0; // d1为最长路径,d2为次长路径
    
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (j == f) continue;
        int d = dfs(j, u) + w[i];
        dist = max(dist, d);
        if (d >= d1) d2 = d1, d1 = d;
        else if (d > d2) d2 = d;
    }
    ans = max(ans, d1 + d2);
    return dist;
}
int main () {
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i < n; i ++ ) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    dfs(1, -1);
    
    cout << ans << endl;
}

// 

4.5 状压DP

4.6 数位DP

// 统计0~N有多少位含i
int dgt(int n) {
    int res = 0;
    while (n) ++ res, n /= 10;
    return res;
}

int count (int n, int i) {
    int res = 0, d = dgt(n);
    for (int j = 1; j <= d; j ++ ) {
        int p = pow(10, j - 1), l = n / p / 10, r = n % p, dj = n / p % 10;
        if (i) res += l * p; 
        if (!i && l) res += (l - 1) * p; 
        if ( (dj > i) && (i || l) ) res += p;
        if ( (dj == i) && (i || l) ) res += r + 1;
    }
    return res;
}

// 统计数位上至少有k个某数
#include <bits/stdc++.h>
using namespace std;

const int maxn = 210;
int f[maxn][maxn];
int k, b;

void init () {
        for (int i = 0; i < maxn; i ++ ) {
                for (int j = 0; j <= i; j ++ ) {
                        if (!j) f[i][j] = 1;
                        else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
                }
        }
}

int dp (int n) {
        if (!n) return 0;
        
        vector<int> nums;
        while (n) nums.push_back(n % b), n /= b;
        
        int res = 0;
        int ls = 0;
        
        for (int i = nums.size() - 1; i >= 0; i -- ) {
                int x = nums[i];
                if (x) {
                        res += f[i][k - ls];
                        if (x > 1) {
                                if (k - ls - 1 >= 0) res += f[i][k - ls - 1];
                                break;
                        }
                        else {
                                ls ++ ;
                                if (ls > k) break;
                        }
                }
                if (!i && ls == k) res ++ ;
        }
        
        return res;
}

int main () {
        init ();
        int l, r;
        cin >> l >> r >> k >> b;
        cout << dp(r) << ' ' << dp(l - 1) << endl;
}


// 统计l~r中有多少个满足条件的数(单调增,减,不出现某数等)
#include <bits/stdc++.h>
using namespace std;

const int maxn = 15;

int f[maxn][maxn];

void init () {
        for (int i = 0; i <= 9; i ++ ) f[1][i] = 1;
        
        for (int i = 2; i < maxn; i ++ ) {
                for (int j = 0; j <= 9; j ++ ) {
                        for (int k = j; k <= 9; k ++ ) {
                                f[i][j] += f[i - 1][k];
                        }
                }
        }
}

int dp(int n) {
        if (!n) return 1;
        
        vector<int> nums;
        while (n) nums.push_back(n % 10), n /= 10;
        int res = 0;
        int ls = 0;
        
        for (int i = nums.size() - 1; i >= 0; i -- ) {
                int x = nums[i];
                for (int j = ls; j < x; j ++ ) {
                        res += f[i + 1][j];
                }
                
                if (x < ls) break;
                ls = x;
                
                if (!i) res ++ ;
        }
        
        return res;
}

int main () {
        init();
        int l, r;
        while (cin >> l >> r) {
                cout << dp(r) - dp(l - 1) << endl;
        }
}

4.7 单调队列优化DP

//单调队列常用来优化:i的前m个范围内区间的最值问题。(注意,此处的范围都是一个定值

//n个物品中,连续m个中至少选出一个,总价值最小。
//或  不能连续k个在一起,即至少从k+1个中选出一个不选。
#include <bits/stdc++.h>
using namespace std;

const int maxn = 200010;
int n, m;
int w[maxn], dp[maxn];
int q[maxn];

int main () {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
        
        int hh = 0, tt = 0;
        for (int i = 1; i <= n; i ++ ) {
                while (hh <= tt && i - q[hh] > m) hh ++ ;
                dp[i] = dp[q[hh]] + w[i];
                while (hh <= tt && dp[q[tt]] >= dp[i]) tt -- ;
                q[ ++ tt] = i;
        }
        
        if (n + 1 - m > q[hh]) hh ++ ;
        printf("%d\n", dp[q[hh]]);
}

4.8 斜率优化DP

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1000010;
int n, S;
int q[maxn];
int st[maxn], sc[maxn], f[maxn];

signed main () {
        scanf("%lld%lld", &n, &S);
        for (int i = 1; i <= n; i ++ ) {
                scanf("%lld %lld", st + i, sc + i);
                st[i] += st[i - 1];
                sc[i] += sc[i - 1];
        }
        
        int hh = 0, tt = 0;
        q[0] = 0;
        
        for (int i = 1; i <= n; i ++ ) {
                while (hh < tt && (f[q[hh + 1]] - f[q[hh]]) <= (sc[q[hh + 1]] - sc[q[hh]]) * (st[i] + S)) // 由于K单调,因此直接维护。否则可以二分
                hh ++ ;
                f[i] = f[q[hh]] + S * (sc[n] - sc[q[hh]]) + st[i] * (sc[i] - sc[q[hh]]);
                while (hh < tt && (f[q[tt]] - f[q[tt - 1]]) * (sc[i] - sc[q[tt]]) >= (f[i] - f[q[tt]]) * (sc[q[tt]] - sc[q[tt - 1]])) // 维护下凸包
                tt -- ;
                q[ ++ tt] = i; 
        }
        
        printf("%lld\n", f[n]);
}

5. 字符串

5.1 KMP

const int mxn = 1e6 + 100;
struct KMP {
	int ne[mxn], len;
	string t;
	void clear() {
		len = ne[0] = ne[1] = 0;
	}
	
    //下标从1开始
    //string 转 char ,在末尾加'\0'
	void init (string s) {
		len = sz(s) - 1;
        t = s;
		for (int i = 2; i <= len; i ++ ) {
			ne[i] = ne[i - 1];
			while (ne[i] && s[i] != s[ne[i] + 1]) ne[i] = ne[ne[i]];
			ne[i] += (s[i] == s[ne[i] + 1]);
		}
	}
	
    // 求所有在s串中的start_pos.
	vector<int> match (string s) {
		int len_s = sz(s) - 1;
		vector<int> st_pos(0);
		for (int i = 1, j = 1; i <= len_s;) {
			while (j != 1 && s[i] != t[j]) j = ne[j - 1] + 1;
			if (s[i] == t[j]) j ++ , i ++ ;
			else i ++ ;
			if (j == len + 1) {
				st_pos.pb(i - j + 1);
				j = ne[len] + 1;
			}
		}
		return st_pos;
	}
	
	void debug () {
		for (int i = 0; i <= len; i ++ ) {
			printf("[debug] nxt[%d]=%d\n", i, ne[i]);
		}
	}
	
   /* 循环周期 形如 acaca 中 ac 是一个合法周期 */
	vector<int> periodic() {
		vector<int> ret;
		int now = len;
		while (now) {
			now = ne[now];
			ret.pb(len - now);
		}
		return ret;
	}
    
	/* 循环节 形如 acac 中ac、acac是循环节,aca不是*/
	vector<int> periodic_loop() {
		vector<int> ret;
		for (int x : periodic()) {
			if (len % x == 0) ret.pb(x);
		}
		return ret;
	}
	int min_periodic_loop() {
		return periodic_loop()[0];
	}
}kmp;

5.2 Manacher

struct Manacher {
	int lc[maxn];
	string ch;
	int N;
	Manacher(string s) {init(s); manacher();}
	/* s 1 bas */
	void init (string s) {
		int n = sz(s) - 1;
		ch.resize(n * 2 + 10);
		ch[n * 2 + 1] = '#';
		ch[0] = '@';
		ch[n * 2 + 2] = '\0';
		for (int i = n; i >= 1; i -- ) {
			ch[i * 2] = s[i]; ch[i * 2 - 1] = '#';
		}
		N = 2 * n + 1;
	}
	void manacher() {
		lc[1] = 1; int k = 1;
		for (int i = 2; i <= N; i ++ ) {
			int p = k + lc[k] - 1;
			if (i <= p) {
				lc[i] = min(lc[2 * k - i], p - i + 1);
			}
			else lc[i] = 1;
			while (ch[i + lc[i]] == ch[i - lc[i]]) lc[i] ++ ;
			if (i + lc[i] > k + lc[k]) k = i;
		}
	}
	void debug () {
		puts(ch.c_str());
		for (int i = 1; i <= N; i ++ ) {
			printf("lc[%d]=%d\n", i, lc[i]);
		}
	}
};

void solve() {
	string s; cin >> s;
	s = " " + s;
	Manacher manacher(s);
	manacher.debug();
}

5.3 Hash

const int sigma = 60 * 60; /* 字符集大小 */
const int HASH_CNT = 2; /* hash次数 */
int s[maxn];
 /* char* 1-bas
 * sum[i] = s[i]+s[i-1]*Seed+s[i-2]*Seed^2+...+s[1]*Seed^(i-1)*/
ULL Prime_Pool[] = {1998585857ul, 23333333333ul};
ULL Seed_Pool[] = {911, 146527, 19260817, 91815541};
ULL Mod_Pool[] = {29123, 998244353, 1000000009, 4294967291ull};
struct Hash {
	ULL seed, mod;
	ULL base[maxn], sum[maxn];
	int perm[sigma];
	void init (int seedindex, int modindex) {
		seed = Seed_Pool[seedindex], mod = Mod_Pool[modindex];
		base[0] = 1;
		for (int i = 1; i <= n; i ++ ) {
			base[i] = base[i - 1] * seed % mod;
		}
		for (int i = 1; i <= n; i ++ ) {
			sum[i] = (sum[i - 1] * seed % mod + s[i]) % mod;
		}
	}
	/*random_shuffle 离散化id,防止kill_hash*/
	void index_init(int seedindex, int modindex) {
		seed = Seed_Pool[seedindex], mod = Mod_Pool[modindex];
		base[0] = 1;
		for (int i = 1; i <= n; i ++ ) {
			base[i] = base[i - 1] * seed % mod;
		}
		iota (perm + 1, perm + sigma + 1, 1);
		random_shuffle(perm + 1, perm + sigma + 1);
		for (int i = 1; i <= n; i ++ ) {
			sum[i] = (sum[i - 1] * seed % mod + perm[s[i]]) % mod;
		}
	}
	ULL gethash (int l, int r) {
		return (sum[r] - sum[l - 1] * base[r - l + 1] % mod + mod) % mod;
	}
}hasher[HASH_CNT];
inline pair<ULL, ULL> hashrange(int l, int r) {
	return mkp(hasher[0].gethash(l, r), hasher[1].gethash(l, r));
}
map<char, int> id; int idcnt;

void solve() {
	read(n), read(m);
	string a; cin >> a;
	for (int i = 0; i < n; i ++ ) {
		if (!id.count(a[i])) id[a[i]] = ++ idcnt;
		s[i + 1] = id[a[i]];
	}
	for (int i = 0; i < HASH_CNT; i ++ ) hasher[i].index_init(i, i);
	
	while (m -- ) {
		int l1, r1, l2, r2;
		read(l1), read(r1), read(l2), read(r2);
		if (hashrange(l1, r1) == hashrange(l2, r2)) puts("Yes");
		else puts("No");	
	}
}

5.4 AC自动机

#include <bits/stdc++.h>
using namespace std;

const int maxn = 300010;
queue<int> q;
//int vis[maxn]; 用于做出现次数 
struct Aho_Corasick_Automaton {
	int c[maxn][26], fail[maxn], cnt, val[maxn];
//	int flag[maxn], in[maxn], ans[maxn];  // 用于做出现次数  
	
	void ins (string s, int x) { // 不加x,即为出现个数 
		int len = s.size();
		int now = 0;
		for (int i = 0; i < len; i ++ ) {
			int v = s[i] - 'a';
			if (!c[now][v]) c[now][v] = ++ cnt;
			now = c[now][v];
		}
//		if (!flag[now]) flag[now] = x; // 用于做出现次数 
//		val[x] = flag[now];
		
//		val[now] ++ ; // 用于做出现个数 

	}
	
	void build() {
		for (int i = 0; i < 26; i ++ ) {
			if (c[0][i]) {
				fail[c[0][i]] = 0;
				q.push(c[0][i]);
			}
		}
		while (q.size()) {
			int t = q.front(); q.pop();
			for (int i = 0; i < 26; i ++ ) {
				if (c[t][i]) {
					fail[c[t][i]] = c[fail[t]][i];
//					in[fail[c[t][i]]] ++ ; // 用于做出现次数 
					q.push(c[t][i]);
				}
				else c[t][i] = c[fail[t]][i];
			}
		}
	}
	void query (char *s) {
		int len = strlen(s);
		int now = 0;
//		int ans = 0;
		for (int i = 0; i < len; i ++ ) {
			now = c[now][s[i] - 'a'];
			ans[now] ++ ; // 用于做出现次数 
//			for (int t = now; t && ~val[t]; t = fail[t]) {  // 用于做出现个数 
//				
//				ans += val[t], val[t] = -1;
//			}
		}
//		return ans; 最终出现个数 
	}
	
//	void topsort() { // 用于做出现次数 
//		for (int i = 1; i <= cnt; i ++ ) {
//			if (!in[i]) q.push(i);
//		}
//		while (q.size()) {
//			int t = q.front(); q.pop();
//			vis[flag[t]] = ans[t];
//			int v = fail[t];
//			in[v] -- ;
//			ans[v] += ans[t];
//			if (!in[v]) q.push(v);
//		}
//	}
	
	void clear () {
	        cnt = 0;
	        memset(c, 0, sizeof c);
	        memset(val, 0, sizeof val);
	        memset(fail, 0, sizeof fail);
//	        memset(flag, 0, sizeof flag); // 用于做出现次数 
//	        memset(ans, 0, sizeof ans);
//	        memset(in, 0, sizeof in);
	}
}AC;
int n;
string p[200];
char t[1000010];

// vis[AC.val[i]] 第i个串的出现个数. 
int main () {
	while (scanf("%d", &n), n) {
		AC.clear();
		for (int i = 1; i <= n; i ++ ) {
			cin >> p[i];
			AC.ins(p[i], i);
		}
		AC.build();
		scanf("%s", t);
		AC.query(t);
		AC.topsort();
		
		int mx = 0;
		for (int i = 1; i <= n; i ++ ) {
			mx = max(mx, vis[AC.val[i]]);
	//		printf("%d\n", vis[AC.val[i]]);
		}
		
		printf("%d\n", mx);
		for (int i = 1; i <= n; i ++ ) {
			if (vis[AC.val[i]] == mx) cout << p[i] << endl;
		}
	}
}

5.5 最小表示法

#include <bits/stdc++.h>
using namespace std;

char s[1000];

int main () {
	scanf("%s", s + 1);
	int n = strlen(s + 1);
	for (int i = 1; i <= n; i ++ ) {
		s[n + i] = s[i];
	}
	int i = 1, j = 2, k;
	while (i <= n && j <= n) {
		for (k = 0; k < n && s[i + k] == s[j + k]; k ++ );
		if (k == n) break;
		if (s[i + k] > s[j + k]) {
			i += k + 1;
			if (i == j) i ++ ;
		}
		else {
			j += k + 1;
			if (i == j) j ++ ;
		}
	}
	int ans = min(i, j);
	for (int i = ans; i <= ans + n - 1; i ++ ) printf("%c", s[i]);
}

5.6 Trie

// 字典树
const int mxn = 1e6 + 100;
struct Trie {
	int nxt[mxn << 1][26], cnt[mxn << 1];
	int c = 0;
	void clear() {
	        c = 0;
		ms(nxt[0], 0);
		ms(cnt, 0);
	}
	
	inline int newnode () {
		c ++ ;
		return c;
	}
	
	void insert (string s) {
		int u = 0, now = 0;
		while (now < sz(s)) {
			u = insert(u, s[now] - 'a');
			now ++ ;
		}
		cnt[u] ++ ;
	}
	inline int insert (int pre, int ch) {
		return nxt[pre][ch] ? nxt[pre][ch] : nxt[pre][ch] = newnode();
	}
	
	inline int query (string s) {
		int u = 0, now = 0;
		while (now < sz(s)) {
			if (!nxt[u][s[now] - 'a']) return 0;
			else u = nxt[u][s[now] - 'a'];
			now ++ ;
		}
		return cnt[u];
	}
}trie;

void solve() {
        read(n);
        for (int i = 1; i <= n; i ++ ) {
                string op, a;
                cin >> op >> a;
                if (op == "I") trie.insert(a);
                else printf("%lld\n", trie.query(a));
        }
}


// 01trie 最大区间异或和
const int mxn = 5e5 + 100;
struct Tire {
	int nxt[mxn << 2][2], l[mxn << 2];
	int cnt, ansl, ansr, ansv;
	
	inline void init () {
		cnt = 0; ansv = -1;
		ms(nxt[0], 0);
		ms(l, 0x3f);
	}
	
	inline int create () {
		cnt ++ ;
		ms(nxt[cnt], 0);
		return cnt;
	}
	
	inline void insert (int id, int x) {
		int u = 0;
		for (int i = 31; i >= 0; i -- ) {
			int t = ((x >> i) & 1);
			if (!nxt[u][t]) nxt[u][t] = create();
			u = nxt[u][t];
		}
		l[u] = id;
//		l[u] = min(l[u], id);
	}
	inline void query (int id, int x) {
		int u = 0, res = 0;
//		de(ansv); de(x);
		for (int i = 31; i >= 0; i -- ) {
			int t = ((x >> i) & 1);
			if (nxt[u][!t]) {
				u = nxt[u][!t];
				res += 1ll << i;
			}
			else u = nxt[u][t];
		}
//		de(id); de(res);
//		if (res == ansv) {
//			if (l[u] < ansl) {
//				ansl = l[u]; ansr = id;
//			}
//		}
		if (res > ansv) {
			ansv = res; ansl = l[u]; ansr = id;
		}
	}
}trie;

void solve() {
	read(n);
	trie.init(); trie.insert(0, 0);
	int sum = 0;
	for (int i = 1; i <= n; i ++ ) {
		int x; read(x); sum ^= x;
		trie.query(i, sum); trie.insert(i, sum);
	}
	printf("%lld %lld %lld\n", trie.ansv, trie.ansl + 1, trie.ansr);
}


// 可持久化01trie
const int mxn = 6e5 + 100;
struct Tire {
	int nxt[mxn * 25][2], sum[mxn * 25];
	int root[mxn * 25];
	int cnt;
	
	inline void init () {
		cnt = 0;
		ms(nxt[0], 0);
		ms(root, 0);
		ms(sum, 0);
	}
	
	inline int create () {
		cnt ++ ;
		ms(nxt[cnt], 0);
		return cnt;
	}
	
	inline int insert (int x, int pre) {
		int u = ++ cnt, t = u;
		for (int i = 30; i >= 0; i -- ) {
			int t = ((x >> i) & 1);
			nxt[u][0] = nxt[pre][0], nxt[u][1] = nxt[pre][1];
			sum[u] = sum[pre] + 1;
			nxt[u][t] = ++ cnt;
			u = nxt[u][t], pre = nxt[pre][t];
		}
		sum[u] = sum[pre] + 1;
		return t;
	}
	
	inline int query (int x, int l, int r) {
		int res = 0;
		for (int i = 30; i >= 0; i -- ) {
			int t = !((x >> i) & 1);
			if (sum[nxt[r][t]] - sum[nxt[l][t]] > 0) {
				res |= (1ll << i);
				l = nxt[l][t], r = nxt[r][t];
			}
			else l = nxt[l][!t], r = nxt[r][!t];
		}
		return res;
	}
}trie;

void solve() {
	read(n), read(m);
	int now = 0;
	n ++ ;
	trie.root[1] = trie.insert(now, trie.root[0]);
	for (int i = 2; i <= n; i ++ ) {
		int x; read(x);
		now ^= x;
		trie.root[i] = trie.insert(now, trie.root[i - 1]);
	}
	
	while (m -- ) {
		char op[2];
		scanf("%s", op);
		if (*op == 'A') {
			int x; read(x); now ^= x;
			n ++ ;
			trie.root[n] = trie.insert(now, trie.root[n - 1]);
		}
		else {
			int l, r, x;
			read(l), read(r), read(x);
			int tmp = now ^ x;
			ll ans = trie.query(tmp, trie.root[l - 1], trie.root[r]);
			printf("%lld\n", ans);
		}
	}
}

6 杂项

6.1 高精度

__int128使用(kuangbin模板)(范围:-2^127 ~ 2^127, 约10^38,longlong范围10^19)

#include <bits/stdc++.h>
using namespace std;
inline __int128 read(){
    	__int128 x = 0, f = 1;
    	char ch = getchar();
    	while(ch < '0' || ch > '9'){
        		if(ch == '-') f = -1;
        		ch = getchar();
    	}
    	while(ch >= '0' && ch <= '9'){
        		x = x * 10 + ch - '0';
        		ch = getchar();
    	}
    	return x * f;
}
inline void print(__int128 x){
        if(x < 0){
                putchar('-'); x = -x;
        }
        if(x > 9) print(x / 10);
        putchar(x % 10 + '0');
}
int main(void){
    __int128 a = read();
    __int128 b = read();
    print(a + b);
    cout << endl;
    return 0;
}

大整数类(压9位)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Wint:vector<ll>
{
    const static ll BIT=1e9;
    Wint(ll n=0) {push_back(n);check();}
    Wint& operator=(const char* num)
    {
        int Len=strlen(num)-1; clear();
        for(int i=Len;i>=0;i-=9)
        {
            push_back(0); ll w=1;
            for(int j=i;j>i-9&&j>=0;--j)
                back()+=(num[j]^48)*w,w*=10;
        }
        return *this;
    }
    Wint& check()
    {
        while(!empty()&&!back()) pop_back();
        if(empty()) return *this;
        for(int i=1;i<size();++i)
            (*this)[i]+=(*this)[i-1]/BIT,
            (*this)[i-1]%=BIT;
        while(back()>=BIT)
        {
            push_back(back()/BIT);
            (*this)[size()-2]%=BIT;
        }
        return *this;
    }
};
bool operator<(Wint a,Wint b)
{
    if(a.size()!=b.size()) return a.size()<b.size();
    for(int i=a.size()-1;i>=0;--i)
        if(a[i]!=b[i]) return a[i]<b[i];
    return 0;
}
bool operator>(Wint a,Wint b) {return b<a;}
bool operator<=(Wint a,Wint b) {return !(a>b);}
bool operator>=(Wint a,Wint b) {return !(a<b);}
bool operator!=(Wint a,Wint b) {return a<b||b<a;}
bool operator==(Wint a,Wint b) {return !(a<b)&&!(b<a);}
Wint& operator+=(Wint &a,Wint b)
{
    if(a.size()<b.size()) a.resize(b.size());
    for(int i=0;i<b.size();++i) a[i]+=b[i];
    return a.check();
}
Wint operator+(Wint a,Wint b) {return a+=b;}
Wint& operator-=(Wint &a,Wint b)
{
    for(int i=0;i<b.size();a[i]-=b[i],++i)
        if(a[i]<b[i])
        {
            int j=i+1;
            while(!a[j]) ++j;
            while(j>i) --a[j],a[--j]+=Wint::BIT;
        }
    return a.check();
}
Wint operator-(Wint a,Wint b) {return a-=b;}
Wint operator*(Wint a,Wint b)
{
    if(a.empty()&&b.empty()) return a;
    Wint n; n.assign(a.size()+b.size()-1,0);
    for(int i=0;i<a.size();++i)
        for(int j=0;j<b.size();++j)
            n[i+j]+=a[i]*b[j];
    return n.check();
}
Wint& operator*=(Wint &a,Wint b) {return a=a*b;}
Wint operator/(Wint a,int b)
{
    Wint n; bool wp=0; ll t=0;
    for(int i=a.size()-1;i>=0;--i)
    {
        t=t*Wint::BIT+a[i];
        if(wp||t/b) wp=1,n.push_back(t/b);
        t%=b;
    }
    reverse(n.begin(),n.end());
    return n;
}
Wint& operator/=(Wint &a,int b) {return a=a/b;}
void readX(Wint &n) {char s[100010]; scanf("%s",s); n=s;}
void writeX(Wint n)
{
    if(n.empty()) {putchar('0'); return;}
    int Len=n.size()-1; printf("%lld",n[Len]);
    for(int i=Len-1;i>=0;--i) printf("%09lld",n[i]);
}

6.2 二维前缀和

//构造前缀和矩阵
a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];

//求x1, y1, x2, y2为边界的子矩阵之和
a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];

6.3 双指针

6.4 ST表

// 可换成 max, min, gcd
void init () {
        for (int j = 0; j < 20; j ++ ) {
                for (int i = 1; i + (1 << j) - 1 <= n; i ++ ) {
                        if (!j) dp[i][j] = q[i];
                        else dp[i][j] = max(dp[i][j - 1], dp[i + (1 << j - 1)][j - 1]);
                }
        }
}

int query (int l, int r) {
        int k = log2(r - l + 1);
        return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}

6.5 快读函数 read()

// 仅支持整型
template <typename _T>
inline void read(_T &f) {
    f = 0; _T fu = 1; char c = getchar();
    while (c < '0' || c > '9') { if (c == '-') { fu = -1; } c = getchar(); }
    while (c >= '0' && c <= '9') { f = (f << 3) + (f << 1) + (c & 15); c = getchar(); }
    f *= fu;
}

//重载流输入型
struct IOS{
    template<typename ATP>IOS& operator >> (ATP &x){
        x = 0; int f = 1; char c;
        for(c = getchar(); c < '0' || c > '9'; c = getchar()) if(c == '-')  f = -1;
        while(c >= '0' && c <= '9') x = x * 10 + (c ^ '0'), c = getchar();
        x*= f;
        return *this;
    }
}io;
io >> n;

6.6 模拟退火

/*
求费马点型

在二维平面上有 n 个点,第 i 个点的坐标为 (xi,yi)。

请你找出一个点,使得该点到这 n 个点的距离之和最小。

该点可以选择在平面中的任意位置,甚至与这 n 个点的位置重合。
*/

#define x first
#define y second
typedef pair<double, double> PII;
const int maxn = 110;
int n;
PII q[maxn];
double ans = 1e8;

double rand (double l, double r) {
        return (double)rand() / RAND_MAX * (r - l) + l;
}

double dis (PII a, PII b) {
        return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

double calc (PII p) {
        double res = 0;
        for (int i = 1; i <= n; i ++ ) {
                res += dis(p, q[i]);
        }
        ans = min(ans, res);
        return res;
}

void simulate_anneal () {
        PII cur(rand(0, 10000), rand(0, 10000));
        for (double t = 1e4; t > 1e-4; t *= 0.9) {
                PII np(rand(cur.x - t, cur.x + t), rand(cur.y - t, cur.y + t));
                double d = calc(np) - calc(cur);
                if (exp(-d / t) > rand(0, 1)) cur = np;
        }
}

int main () {
        srand(time(NULL));
        scanf("%d", &n);
        for (int i = 1; i <= n; i ++ ) scanf("%lf%lf", &q[i].first, &q[i].second);
        
    	// 或 while ((double)clock()/CLOCKS_PER_SEC<0.8)
        for (int i = 1; i <= 100; i ++ ) 
                simulate_anneal();
    
        printf("%.0lf\n", ans);
}

6.7 STL

vector, 变长数组,倍增的思想
		size()  返回元素个数
		empty()  返回是否为空
		clear()  清空
		front()/back()
		push_back()/pop_back()
		begin()/end()
		[]
		支持比较运算,按字典序

	pair<int, int>
		first, 第一个元素
		second, 第二个元素
		支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

	string,字符串
    	int has = s.find('xxxx');
		if (has != string::npos) 含xxx字符
		szie()/length()  返回字符串长度
		empty()
		clear()
		substr(起始下标,(子串长度))  返回子串
		c_str()  返回字符串所在字符数组的起始地址 代表可以用printf输出string类

	queue, 队列
		size()
		empty()
		push()  向队尾插入一个元素
		front()  返回队头元素
		back()  返回队尾元素
		pop()  弹出队头元素

	priority_queue, 优先队列,默认是大根堆
		push()  插入一个元素
		top()  返回堆顶元素
		pop()  弹出堆顶元素
		定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;

	stack,size()
		empty()
		push()  向栈顶插入一个元素
		top()  返回栈顶元素
		pop()  弹出栈顶元素

	deque, 双端队列
		size()
		empty()
		clear()
		front()/back()
		push_back()/pop_back()
		push_front()/pop_front()
		begin()/end()
		[]

	set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
		size()
		empty()
		clear()
		begin()/end()
		++, -- 返回前驱和后继,时间复杂度 O(logn)
		
		set/multiset
			insert()  插入一个数
			find()  查找一个数
			count()  返回某一个数的个数
			erase()
				(1) 输入是一个数x,删除所有x   O(k + logn)
				(2) 输入一个迭代器,删除这个迭代器
			lower_bound()/upper_bound()
				lower_bound(x)  返回大于等于x的最小的数的迭代器
				upper_bound(x)  返回大于x的最小的数的迭代器
		map/multimap
			insert()  插入的数是一个pair
			erase()  输入的参数是pair或者迭代器
			find()
			[]   时间复杂度是 O(logn)
			lower_bound()/upper_bound()

	unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
		和上面类似,增删改查的时间复杂度是 O(1)
		不支持 lower_bound()/upper_bound(), 迭代器的++--


            
bit.size()       返回大小(位数)
bit.count()     返回1的个数
bit.any()       返回是否有1
bit.none()      返回是否没有1
bit.set()       全都变成1
bit.set(p)      将第p + 1位变成1(bitset是从第0位开始的!) 
bit.set(p, x)   将第p + 1位变成x
bit.reset()     全都变成0
bit.reset(p)    将第p + 1位变成0
bit.flip()      全都取反
bit.flip(p)     将第p + 1位取反
bit.to_ulong()  返回它转换为unsigned long的结果,如果超出范围则报错
bit.to_ullong() 返回它转换为unsigned long long的结果,如果超出范围则报错
bit.to_string() 返回它转换为string的结果

6.8 离散化

vector<int> v;
	for (int i = 1; i <= n; i ++ ) {
		read(q[i]); v.pb(q[i]);
	}
	sort(all(v));
	v.erase(unique(all(v)), v.end());
	for (int i = 1; i <= n; i ++ ) {
		q[i] = lower_bound(all(v), q[i]) - v.begin() + 1;
	}

6.9 随机数

mt19937 sed(time(nullptr));
uniform_int_distribution<int> range(l, r); // l到r内随机数
cout << range(sed) << endl; 

文章作者: Cedeat
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cedeat !
  目录