算法模板

54 阅读8分钟

基础算法

二分

左边界

int l = 0, r = n - 1;
while (l < r){
    int mid = (l + r) >> 1;
    if (check(mid)) r = mid;
    else l = mid + 1;
}

右边界

int l = 0, r = n - 1;
while (l < r){
    int mid = (l + r + 1) >> 1;
    if (check(mid)) l = mid;
    else r = mid - 1;
}

三分

double l = 0 , r = 1000 , exp = 0.00000000001;
while (r - l > exp){
        double mid1 = l+(r-l) / 3;			// 取 1/3
        double mid2 = r-(r-l) / 3;			// 取 2/3
        if (check(mid1) > check(mid2)) l = mid1;
        else
                r = mid2;
}

注: exp 误差进度取题目要求答案后两位。例如向后取4位, 只需要向后取6位。

排序

快速排序

struct Range {
    int start, end;

    Range(int s = 0, int e = 0) { start = s, end = e; }
};

template <typename T>
void quick_sort(T arr[], const int len) {
    if (len <= 0) return;
    Range r[len];
    int p = 0;
    r[p++] = Range(0, len - 1);
    while (p) {
        Range range = r[--p];
        if (range.start >= range.end) continue;
        T mid = arr[range.end];
        int left = range.start, right = range.end - 1;
        while (left < right) {
            while (arr[left] < mid && left < right) left++;
            while (arr[right] >= mid && left < right) right--;
            std::swap(arr[left], arr[right]);
        }
        if (arr[left] >= arr[range.end])
            std::swap(arr[left], arr[range.end]);
        else
            left++;
        r[p++] = Range(range.start, left - 1);
        r[p++] = Range(left + 1, range.end);
    }
}

快选算法

int quick_sort(int l , int r , int k){

    if(l == r) return q[l];

    int i = l - 1 , j = r + 1 , x = q[l + r >> 1];

    while(i < j){
        while(q[++i] < x);
        while(q[--j] > x);
        if(i < j) swap(q[i] , q[j]);
    }

    int sl = j - l + 1;

    if(k <= sl)  return quick_sort(l , j , k);

    return quick_sort(j + 1 , r , k - sl);
}

归并排序

void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;

    merge_sort(q, l, mid), merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];             //res += mid - i + 1; 逆序对数量
    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}

二维前缀和

for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
    s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);

二维差分

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            insert(i, j, i, j, a[i][j]);

    while (q -- )
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c);
    }

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];

insert 函数

void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x2 + 1][y1] -= c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y2 + 1] += c;
}

双指针

最大不连续子段

    for(int i=1 , j = 1; i <= n; i++){
        s[a[i]] ++;
        while (s[a[i]] > 1)
        {
            s[a[j]] --;             // 删掉前面重复的数
            j ++;
        }

        res = max(res , i - j + 1);
    }

离散化

  1. 排序 , 使用sort 函数
  2. 去重 , 使用unique 函数
  3. 二分索引 , 推荐可以使用lower_bound , 不需要写二分函数。 [左闭右开]

高精度

加法

不压位

vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);

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

    if (t) C.push_back(t);
    return C;
}

压9位

#include <iostream>
#include <vector>

using namespace std;

const int base = 1000000000;

vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);

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

    if (t) C.push_back(t);
    return C;
}

int main()
{
    string a, b;
    vector<int> A, B;
    cin >> a >> b;

    for (int i = a.size() - 1, s = 0, j = 0, t = 1; i >= 0; i -- )
    {
        s += (a[i] - '0') * t;
        j ++, t *= 10;
        if (j == 9 || i == 0)
        {
            A.push_back(s);
            s = j = 0;
            t = 1;
        }
    }
    for (int i = b.size() - 1, s = 0, j = 0, t = 1; i >= 0; i -- )
    {
        s += (b[i] - '0') * t;
        j ++, t *= 10;
        if (j == 9 || i == 0)
        {
            B.push_back(s);
            s = j = 0;
            t = 1;
        }
    }

    auto C = add(A, B);

    cout << C.back();
    for (int i = C.size() - 2; i >= 0; i -- ) printf("%09d", C[i]);
    cout << endl;

    return 0;
}

减法

#include <iostream>
#include <vector>

using namespace std;

bool cmp(vector<int> &A, vector<int> &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 true;
}

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

    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

int main()
{
    string a, b;
    vector<int> A, B;
    cin >> a >> b;
    for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');

    vector<int> C;

    if (cmp(A, B)) C = sub(A, B);          // 注意这里需要比较一下
    else C = sub(B, A), cout << '-';

    for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
    cout << endl;

    return 0;
}

乘法

vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;

    int t = 0;
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();

    return C;
}

除法

给定两个非负整数(不含前导 00) A , B ,请你计算 A/B 的商和余数。

输入格式

共两行,第一行包含整数 A,第二行包含整数 B

输出格式

共两行,第一行输出所求的商,第二行输出所求余数。

数据范围

1A100000,1B10000 1 ≤ |A| ≤100000 , 1≤ |B| ≤10000
B一定不为 0

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

高精度取模

std::string s;
std::cin >> s;

LL sum = 0;
for (int i = 0; i < int(s.size()); i ++ ){
    int c = s[i] - '0';
    sum = (sum * 10 % mod + c) % mod;
}

线性数据结构

单调栈

#include <iostream>
#include <stack>
using namespace std;
int n;
stack <int> st;
int main () {
   cin >> n;
   for (int i = 1;i <= n;i++) {
       int x;
       cin >> x;
       while (!st.empty () && st.top () >= x) st.pop ();
       if (!st.empty ()) cout << st.top () << ' ';
       else cout << -1 << ' ';
       st.push (x);
   }
   return 0;
}

单调队列

给定定长长度为k的窗口 , 输出序列中的最大(小)值

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <vector>

int n , k;

int main(){
	
	std::ios::sync_with_stdio(0);
	std::cout.tie(0);
	std::cin.tie(0);
	
	std::cin >> n >> k;
	std::vector<int> v(n);
	for (int i = 0; i < n; i ++ )
		std::cin >> v[i];
	
	
	std::deque<int> q;
	for (int i = 0; i < n; i ++ ){
		if (q.size() && q.front() < i - k + 1) q.pop_front();
		while (q.size() && v[q.back()] >= v[i]) q.pop_back();
		q.push_back(i);
		
		if (i - k + 1 >= 0) std::cout << v[q.front()] << ' ';
	}
	
	std::cout << '\n';
	
	q.clear();
	for (int i = 0; i < n; i ++ ){
		if (q.size() && q.front() < i - k + 1) q.pop_front();
		while (q.size() && v[q.back()] <= v[i] ) q.pop_back();
		q.push_back(i);
		if (i - k + 1 >= 0) std::cout << v[q.front()] << ' ';
	}
	
	return 0;
} 

(单)链表

#include <iostream>
#include <cstring>

using namespace std;

const int N = 100010;

int m;
int head , e[N] , ne[N] , idx;

void init(){
    head = -1;
    idx = 0;
}

void add_to_head(int x){
    e[idx] = x , ne[idx] = head , head = idx ++;
}

void remove(int k){
    ne[k] = ne[ne[k]];
}

void insert(int k , int x){
    e[idx] = x , ne[idx] = ne[k] , ne[k] = idx ++;
}

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

    init();
    
    while(m --){
        
        int k , x;
        char op;
        cin >> op;
        
        if(op == 'H'){
            cin >> x;
            add_to_head(x);
        }
        else if(op == 'D'){
            cin >> k;
            if(!k) head =  ne[head];
            else remove(k - 1);
        }
        else{
            cin >> k >> x;
            insert(k - 1 , x);
        }
    }
    
    for (int i = head; i != -1; i = ne[i])
        cout << e[i] << ' ';
    
    return 0;
}

字符串

KMP

  1. 求字符串t在字符串s中出现的次数
  2. 所有出现的位置
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010 , M = 1000010;

int n , m;
int ne[N];
char p[N] , s[M];


int main(){
    
    scanf("%d%s%d%s" , &n , p + 1 , &m , s + 1);
    
    for (int i = 2 , j = 0; p[i]; i ++){
        
        while(j && p[i] != p[j + 1]) j = ne[j];
        
        if(p[i] == p[j + 1]) j ++;
        
        ne[i] = j;
    }
    
    for (int i = 1 , j = 0; s[i] ; i ++){
        
        while(j && s[i] != p[j + 1]) j = ne[j]
        
        if(s[i] == p[j + 1]) j ++;
        
        if(j == n){
            printf("%d " , i - n);
            j = ne[j];                  // 继续下一次匹配
        }
    }
    
    return 0;
}

Trie 树

维护一个字符串集合,支持两种操作:

  1. I x 向集合中插入一个字符串 xx
  2. Q x 询问一个字符串在集合中出现了多少次。

共有 N𝑁 个操作,所有输入的字符串总长度不超过 105105,字符串仅包含小写英文字母。

const int N = 100010  , M = 20010;

int n , idx;
int son[N][26] , cnt[N];
char str[M];


void insert(char * str){
    int p = 0;

    for (int i = 0; str[i] ; i++){
        int u = str[i] - 'a';
        if(!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }

    cnt[p] ++;
    
}

int query(char str[]){
    int p = 0;

    for (int i = 0;  str[i] ; i++){
        int u = str[i] - 'a';
        if(!son[p][u])  return 0;
        p = son[p][u];
    }

    return cnt[p];
    
}

AC 自动机

给定 n 个模式串 s 和一个文本串 t,求有多少个不同的模式串在文本串里出现过。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1000100 , M = 30;

int n , idx;
char s[N];
struct node{
    int son[M];
    int fail , cnt;
}tr[N];

void insert(char * s){

    int p = 0;
    for(int i = 0; s[i] ; i ++ ){
        int u = s[i] - 'a';
        if(!tr[p].son[u]) tr[p].son[u] = ++idx;
        p = tr[p].son[u];
    }

    tr[p].cnt ++;
}

void build(){

    queue<int> q;

    for (int i = 0; i < 26; i ++ )
        if(tr[0].son[i])
            q.push(tr[0].son[i]);

    while(q.size()){

        int u = q.front();
        q.pop();

        for (int i = 0; i < 26; i ++ ){
            if(tr[u].son[i])
                tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i] , q.push(tr[u].son[i]);
            else
                tr[u].son[i] = tr[tr[u].fail].son[i];
        }
    }

}

int query(char * s){

    int  p = 0 , ans  = 0;

    for (int i = 0; s[i] ; i ++ ){
        p = tr[p].son[s[i] - 'a'];

        for (int j = p; j && tr[j].cnt != -1; j = tr[j].fail)
            ans += tr[j].cnt , tr[j].cnt = -1;
    }

    return ans;
}


int main(){

    scanf("%d" , &n);

    for (int i = 0; i < n; i ++ ){
        scanf("%s" , s);
        insert(s);
    }

    build();

    scanf("%s" , s);

    int t = query(s);

    printf("%d" , t);

    return 0;
}

Manacher

求最长回文子串

#include <iostream>
#include <cstring>

using namespace std;

const int N = 11000010;

char a[N] , s[N * 2];
int d[N * 2];

int manacher(){
	
	int k = 0 , n = strlen(a + 1);
	s[0] = '$' , s[++k] = '#';
	for (int i = 1; i <= n; i ++ )
		s[++k] = a[i] , s[++k] = '#';
	n = k;
	
	int ans = 1;
	d[1] = 1;
	for (int i = 2 , l , r = 1;  i <= n; i ++ ){
		if(i <= r) d[i] = min(d[r - i + l] , r - i + 1);
		while(s[i - d[i]] == s[i + d[i]]) d[i] ++;
		if(i + d[i] - 1 > r) 
			l = i - d[i] + 1 , r = i + d[i] - 1 , ans = max(ans , (r - l + 1) / 2);
	}
	
	return ans;
}

int main(){
	
	scanf("%s" , a + 1);
	
	int t = manacher();
	
	printf("%d" , t);
	
	return 0;
}

树形数据结构

线段树

区间加乘

#include <iostream>
#include <cstring>
#include <algorithm>


#define ls u << 1
#define rs u << 1 | 1
#define LL long long


const int N = 100010;

int n , m , p;
int a[N];

struct node{
	
	int l , r;
	LL sum , mul , add;

}tr[4 * N];


void pushup(int u){

	tr[u].sum = (tr[ls].sum + tr[rs].sum) % p;

}

void cacl(node &T , LL m , LL a){

	T.sum = ((T.sum * m) % p + (T.r - T.l + 1) * a) % p;
	T.mul = T.mul * m % p;
	T.add = (T.add * m % p + a) % p;

}


void pushdown(int u){

	cacl(tr[ls] , tr[u].mul , tr[u].add);
	cacl(tr[rs] , tr[u].mul , tr[u].add);

	tr[u].mul = 1 , tr[u].add = 0;					// 清除标记
}


void build(int u , int l , int r){

	tr[u] = {l , r , a[l] , 1 , 0};

	if(l == r) return ;

	int mid = (l + r) >> 1;

	build(ls , l , mid);
	build(rs , mid + 1 , r);

	pushup(u);
}

void modify(int u , int l , int r  , LL m , LL a){

	if(tr[u].r < l || tr[u].l > r) return ;
	if(l <= tr[u].l && tr[u].r <= r){
		cacl(tr[u] , m , a);

		return ;
	}

	pushdown(u);

	modify(ls , l , r , m , a);
	modify(rs , l , r , m , a);

	pushup(u);
}	



LL query(int u , int l , int r){

	if(tr[u].r < l || tr[u].l > r) return 0;
	if(l <= tr[u].l && tr[u].r <= r)
		return tr[u].sum % p;

	LL sum = 0;

	pushdown(u);

	sum += query(ls , l , r);
	sum += query(rs , l , r);

	pushup(u);

	return sum % p;
}

int main(){

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

	std::cin >> n >> m >> p;

	for (int i = 1; i <= n; i ++ ) std::cin >> a[i];

	build(1 , 1 , n);	

	while(m -- ){

		int opt , l , r , k;

		std::cin >> opt >> l >> r;

		if(opt != 3){
			std::cin >> k;
			if(opt == 1) modify(1 , l , r , k , 0);			// 区间乘法
			else modify(1 , l , r , 1 , k);				// 区间加法
		}
		else
			std::cout << query(1 , l , r ) << '\n';
	}


	return 0;
}

区间最大子段和

#include <iostream>
#include <cstring>
#include <algorithm>


#define ls u << 1
#define rs u << 1 | 1
#define LL long long


const int N = 500010;
const LL INF = 1E18;


int n , m;
int a[N];

struct node{
	
	int l , r;
	LL sum , lmx  , rmx , mx;

}tr[4 * N];


void pushup(node &T , const node &L , const node &R){

	T.sum = L.sum + R.sum;
	T.mx = std::max(std::max(L.mx , R.mx), L.rmx + R.lmx);
	T.lmx = std::max(L.lmx , L.sum + R.lmx);
	T.rmx = std::max(R.rmx , R.sum + L.rmx);

}

void build(int u , int l , int r){
	tr[u] = {l , r , a[l] , a[l] , a[l] , a[l]};
	if(l == r) return ;

	int mid = (l + r) >> 1;

	build(ls , l , mid);
	build(rs , mid + 1 , r);

	pushup(tr[u] , tr[ls] , tr[rs]);
}


void modify(int u , int x , LL c){

	if(tr[u].l == x && tr[u].r == x) {
		tr[u] = {x , x , c , c , c , c};

		return ;
	}
	

	int mid = (tr[u].l + tr[u].r ) >> 1;

	if(x <= mid) modify(ls , x , c);
	else
		modify(rs , x,  c);

	pushup(tr[u] , tr[ls] , tr[rs]);
}

node query(int u , int l , int r ){

	if(l <= tr[u].l && tr[u].r <= r) return tr[u];

	int mid = (tr[u].l + tr[u].r) >> 1;

	if(r <= mid) return query(ls , l , r);
	if(l > mid) return query(rs , l , r);

	node T ;

	pushup(T , query(ls , l , mid) , query(rs , mid + 1 , r) );

	return T;
}


int main(){

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

	std::cin >> n >> m;

	for (int i = 1; i <= n; i ++ ) std::cin >> a[i];

	build(1 , 1 , n);

	while(m -- ){
		int opt , l , r;
		std::cin >> opt >> l >> r;

		if(opt == 1){
			if(l > r) std::swap(l , r);
			std::cout << query(1 , l , r).mx << '\n';
		}else
			modify(1 , l , r);
	}


	return 0;
}

树状数组

  1. 将某区间每一个数加上 xx
  2. 求出某一个数的值。
#include <iostream>
#include <cstring>
#include <algorithm>

#define LL long long

const int N = 500010;

int n , m;
LL tr[N];


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


void modify(int u , LL k){

	while(u <= n)
		tr[u] += k , u = u + lowbit(u);
}

LL query(int x){			// 求前 x项的和

	LL sum = 0;
	while(x)
		sum += tr[x] , x -= lowbit(x);

	return sum;
}

int main(){

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

	std::cin >> n >> m;

	LL x , t = 0;
	for (int i = 1; i <= n; i ++ ){
		std::cin >> x;
		modify(i , x - t);
		t = x;
	}

	while(m -- ){
		int opt , x , y , k;
		std::cin >> opt >> x;
		
		if(opt == 1){
			std::cin >> y >> k;
			modify(x , k);
			modify(y + 1 , -k);
		}
		else
			std::cout << query(x) << '\n';
	}

	return 0;
} 

二维区间加区间和

typedef long long ll;
ll t1[N][N], t2[N][N], t3[N][N], t4[N][N];

void add(ll x, ll y, ll z) {
  for (int X = x; X <= n; X += lowbit(X))
    for (int Y = y; Y <= m; Y += lowbit(Y)) {
      t1[X][Y] += z;
      t2[X][Y] += z * x;  // 注意是 z * x 而不是 z * X,后面同理
      t3[X][Y] += z * y;
      t4[X][Y] += z * x * y;
    }
}

void range_add(ll xa, ll ya, ll xb, ll yb,
               ll z) {  //(xa, ya) 到 (xb, yb) 子矩阵
  add(xa, ya, z);
  add(xa, yb + 1, -z);
  add(xb + 1, ya, -z);
  add(xb + 1, yb + 1, z);
}

ll ask(ll x, ll y) {
  ll res = 0;
  for (int i = x; i; i -= lowbit(i))
    for (int j = y; j; j -= lowbit(j))
      res += (x + 1) * (y + 1) * t1[i][j] - (y + 1) * t2[i][j] -
             (x + 1) * t3[i][j] + t4[i][j];
  return res;
}

ll range_ask(ll xa, ll ya, ll xb, ll yb) {
  return ask(xb, yb) - ask(xb, ya - 1) - ask(xa - 1, yb) + ask(xa - 1, ya - 1);
}

区间查询和修改的时间复杂度都是log2nlog^2n

区间gcd

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

#define ls u << 1
#define rs u << 1 | 1
#define LL long long


const int N = 500010;

int n , m;
LL a[N];
struct Node{
    int l , r;
    LL sum , d;
}tr[4 * N];

LL gcd(LL a , LL b){
    
    return b ? gcd(b , a % b) : a;
}

void pushup(Node &u , const Node &L , const Node &R){
    
    u.sum = L.sum + R.sum;
    u.d = gcd(L.d , R.d);
    
}

void build(int u , int l , int r ){
    
    tr[u] = {l , r};
    if (l == r) {
        LL b = a[l] - a[l - 1];
        tr[u].sum = b;
        tr[u].d = b;
        return ;
    }
    
    int mid = (l + r) >> 1;
    build(ls , l , mid);
    build(rs , mid + 1 , r);
    
    pushup(tr[u] , tr[ls] , tr[rs]);
}

void modify(int u , int x , LL c){
    
    if(tr[u].l == x && tr[u].r == x) {
        LL t = tr[u].sum + c;
        tr[u] = {x , x , t , t};
        
        return ;
    }
    
    int mid = (tr[u].l + tr[u].r) >> 1;
    
    if (x <= mid) modify(ls , x , c);
    else
        modify(rs , x , c);
    
    pushup(tr[u] , tr[ls] , tr[rs]);
}

Node query(int u , int l , int r){
    
    if (l <= tr[u].l && tr[u].r <= r) return tr[u];
    
    int mid = (tr[u].l + tr[u].r) >> 1;
    
    if (r <= mid) return query(ls , l , r);
    if (l > mid) return query(rs , l , r);
    
    Node T;
    
    pushup(T , query(ls , l , mid) , query(rs , mid + 1 , r));
    
    return T;
}

int main(){
    
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);
        
    std::cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) std::cin >> a[i];
    
    build(1 , 1 , n);    
    
    while(m -- ){
        std::string opt;
        int l , r;
        LL d;
        
        std::cin >> opt >> l >> r ;
        
        if(opt == "C"){
            std::cin >> d;
            modify(1 , l , d);
            if(r + 1 <= n) modify(1 , r + 1, -d);             // 区间转单点修改
        }
        else{
            Node L = query(1 , 1 , l);                  //  [1 , L]
            Node R({0 , 0 , 0 , 0});
            
            if(l + 1 <= r) R = query(1 , l + 1 , r);                //  [L + 1 , R]
            std::cout << std::abs(gcd(L.sum , R.d)) << '\n';
        }
    }
    
    return 0;
}

splay

#include <iostream>

using namespace std;

const int N = 100010 , INF = 0x3f3f3f3f;

struct node {

    int p , s[2];               // 父节点 , 左右孩子
    int val;                // 值
    int size , cnt;

    void init(int v , int fa){
        size = cnt = 1;
        p = fa , val = v;
    }
}tr[N];

int root , idx;
int n;

/**
 * @param x  代表当前子树的根节点
 * 统计这颗子树的大小 size
 */
void pushup(int x){
    tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + tr[x].cnt;
}
/**
 * @param x  传入需要旋转的结点
 * 假设 x 是左儿子 , 那么右旋
 * 假设 x 是右儿子 , 那么左旋
 *
 * @variety
 * \t y 为初始旋转的根\n
 * \t z 为 y为根的这颗子树的根节点\n
 * \t k 代表子树的方向 0 代表左子树 , 1 代表右子树\n
 * \t k ^ 1 代表与 x 子树方向 相反方向的子树 \n  例如 k == 1 代表右子树 , 那么 k ^ 1 指代的左子树
 */
void rotate(int x) {

    int y = tr[x].p , z = tr[y].p , k = tr[y].s[1] == x;

    // x相反方向的子树b , 成为y的与x方向相同方向子树.      即 b 和 y 建立连接
    tr[tr[x].s[k ^ 1]].p = y;
    tr[y].s[k] = tr[x].s[k ^ 1];

    //  x 与 y 建立连接
    tr[y].p = x;
    tr[x].s[k ^ 1] = y;

    // x 与 z 建立连接
    tr[z].s[tr[z].s[1] == y] = x;
    tr[x].p = z;

    // 旋转之后需要 pushup , 因为子树发生改变 , 需要重新统计size

    pushup(y) , pushup(x);                  // 注意受影响的子树 , 自底向上统计 y ---> x
}

/**
 *
 * @param x   代表 x 需要旋转的点
 * @param k   代表需要旋转到下面的点
 */

void splay(int x , int k){

    while(tr[x].p != k){        // 旋转到 k 下面就停止
        int y = tr[x].p ,  z = tr[y].p;
        if(z != k)                          // 说明需要双旋     折转底 , 直转中
            (tr[y].s[0] == x) ^ (tr[z].s[0] == y) ? rotate(x) : rotate(y);
        rotate(x);
    }

    if(k == 0) root = x;                // 把x旋转到根 , 那么更新根
}

/**
 * @param val 需要插入的值
 */

void insert(int val){

    int x  = root , p = 0;

    while(x && tr[x].val != val)
        p = x , x = tr[x].s[val > tr[x].val];

    if(x)
        tr[x].cnt ++;
    else{
        x = ++ idx;              // 建结点
        tr[p].s[val > tr[p].val] = x;                   // 父节点指向子节点
        tr[x].init(val , p);
    }

    splay(x , 0);                   // 重新统计树的 size
}

/**
 * @param val  待查找的值 , 将该旋转到根结点
 */

void find(int val){

    int x = root;
    while(tr[x].s[val > tr[x].val] && val != tr[x].val)
        x = tr[x].s[val > tr[x].val];               // 向下找

    splay(x , 0);               // 1、降低树高  2、好获取答案
}

/**
 * @param val   待查找前驱的值
 * @return      返回前驱的下标
 */

int get_pre(int val){

    find(val);

    int x = root;
    if(tr[x].val < val) return x;

    x = tr[x].s[0];             // 从左子树开始
    while(tr[x].s[1])           // 一直向右走
        x = tr[x].s[1];

    splay(x , 0);               // 调整一下树高

    return x;
}

/**
 * @param val 待查找后继的点
 * @return  返回后继的下标
 */

int get_suc(int val){

    find(val);

    int x = root;
    if(tr[x].val > val) return x;

    x = tr[x].s[1];             // 从右子树开始
    while(tr[x].s[0])           // 一直向左边走
        x = tr[x].s[0];

    splay(x , 0);

    return x;
}


/**
 * @param val 待删除的结点
 */
void del(int val){

    int pre = get_pre(val);
    int suc = get_suc(val);

    splay(pre , 0 ) , splay(suc , pre);

    int del = tr[suc].s[0];             // val 结点

    // splay 因为删除一个结点 , 所以需要splay 重新统计size 信息
    if(tr[del].cnt > 1)
        tr[del].cnt -- , splay(del , 0 );
    else
        tr[suc].s[0] = 0 , splay(suc , 0);
}


/**
 * @param val  待查找排名的值
 * @return  返回该值的排名
 */
int get_rank(int val){         // 获取排名

    insert(val);

    int res = tr[tr[root].s[0]].size;

    del(val);

    return res;                 // 因为还有一个哨兵 , 所以不需要加一
}

/**
 * @param k   查找排名为k的下标
 * @return   返回查找到的值
 */
int get_val(int k){                 // 根据排名找值

    int x = root;

    while(1){
        int y = tr[x].s[0];
        if(tr[y].size + tr[x].cnt < k){     // 说明在右子树
            k -=  tr[y].size + tr[x].cnt;
            x = tr[x].s[1];
        }else
            if(tr[y].size >= k) x = tr[x].s[0];
            else
                break;
    }

    splay(x , 0);

    return tr[x].val;
}



int main(){

    insert(-INF) , insert(INF);

    scanf("%d" , &n);

    int op , x;
    while(n -- ){
        scanf("%d%d" , &op , &x);

        if(op == 1) insert(x);
        else if(op == 2) del(x);
        else if(op == 3) printf("%d\n" , get_rank(x));
        else if(op == 4) printf("%d\n" , get_val(x + 1));     // 注意排名 + 1
        else if(op == 5) printf("%d\n" , tr[get_pre(x)].val);
        else printf("%d\n" , tr[get_suc(x)].val);
    }

    return 0;
}

文艺平衡树

您需要写一种数据结构(可参考题目标题),来维护一个有序数列。

其中需要提供以下操作:翻转一个区间,例如原有序序列是 5 4 3 2 1,翻转区间是 [2,4] 的话,结果是 5 2 3 4 1。

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010 , INF = 0x3f3f3f3f;

int n , m;

struct node{
    int p , s[2];
    int val;
    int size , tag;

    void init(int val , int p){
        this->val = val;
        this->p = p;
        this->size = 1;
    }

}tr[N];

int root , idx;

void pushup(int x){

    tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + 1;

}

void rotate(int x ){

    int  y = tr[x].p , z  = tr[y].p , k = tr[y].s[1] == x;

    // b 与 y 建边
    tr[tr[x].s[k ^ 1]].p = y;
    tr[y].s[k] = tr[x].s[k ^ 1];

    // x 与 y 建边
    tr[x].s[k ^ 1] = y;
    tr[y].p = x;

    // x 与 z 建边
    tr[z].s[tr[z].s[1] == y] = x;
    tr[x].p = z;

    pushup(y) , pushup(x);
}

void splay(int x , int k){              // 将 x 转成 k 的儿子

    while(tr[x].p != k){
        int  y = tr[x].p , z = tr[y].p;
        if(z != k)
            (tr[y].s[1] == x) ^ (tr[z].s[1] == y) ? rotate(x) : rotate(y);              // 直转折 , 折转底
        rotate(x);
    }

    if(k == 0) root = x;
}

void pushdown(int x){

    if(tr[x].tag){
        swap(tr[x].s[0] , tr[x].s[1]);                  // 将两棵子树翻转
        tr[tr[x].s[0]].tag ^= 1;
        tr[tr[x].s[1]].tag ^= 1;
        tr[x].tag ^= 1;
    }
}

void insert(int val){

    int x = root , p = 0;

    while(x) p = x , x = tr[x].s[val > tr[x].val];

    x = ++idx;
    tr[p].s[val > tr[p].val] = x;                   // 父节点 指向 子节点
    tr[x].init(val , p);
    splay(x , 0);
}

int get_k(int k){          // 取得第k大的数 的下标

    int x = root;

    while(1){
        pushdown(x);
        int y = tr[x].s[0];
        if(tr[y].size + 1 < k) k -= (tr[y].size + 1) , x = tr[x].s[1];
        else if(tr[y].size >= k) x = y;
        else
            return x;
    }
}

void output(int x){

    if(x == 0) return ;
    pushdown(x);
    output(tr[x].s[0]);
    if(1 <= tr[x].val && tr[x].val <= n)                // 过滤到-INF , INF
        printf("%d " , tr[x].val);
    output(tr[x].s[1]);
}


int main(){

    scanf("%d%d" , &n , &m);

    insert(-INF) , insert(INF);
    for (int i = 1; i <= n; i ++ ) insert(i);

    while(m -- ){
        int l , r;
        scanf("%d%d" , &l , &r);
        l = get_k(l) , r = get_k(r + 2);                // 夹挤区间 [l , r]

        splay(l , 0) , splay(r , l);

        tr[tr[r].s[0]].tag ^= 1;                     // r 打上标记
    }

    output(root);

    return 0;
}

并查集

集合的合并、查询

权值并查集

参考题星球大战

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

const int N = 500010;

int m;
int p[N] , size[N] , d[N];

int find(int x){

	if (p[x] != x){
		int root = find(p[x]);
		d[x] += d[p[x]];
		p[x] = root;
	}

	return p[x];
}


int main(){

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

	std::cin >> m;

	for (int i = 1; i <= N; i ++ ) {
		p[i] = i;
		size[i] = 1;
	}

	while(m -- ){
		std::string opt;
		int a , b;

		std::cin >> opt >> a >> b;

		int pa = find(a) , pb = find(b);

		if (opt == "M"){
			if (pa != pb){
				p[pa] = pb;
				d[pa] += size[pb];
				size[pb] += size[pa];
			}
		}
		
		if (opt == "C"){
			if(pa != pb) std::cout << -1 << '\n';
			else
				std::cout << std::max(0 , std::abs(d[a] - d[b]) -1 ) << '\n';
		}
		
	}

	return 0;
}

串hash unordered_map

常用操作介绍

主要介绍curd,以及其他好用的函数。

插入元素

// 插入方法 1
un_mp.insert(std::unordered_map<std::string , int>::value_type("test" , 1));
// 插入方法 2
un_mp["test"] = 333;
    
std::cout << un_mp["test"] << '\n';

明显我们发现第二种插入方法更加的方便 un_map[key] = value

删除元素

un_mp["test"] = 333;
un_mp.erase("test");

std::cout << un_mp["test"] << '\n';

注: 如果删除的key 不存在的话 ,那么erase 函数会返回 0 , 其中返回值表示的是删除元素的个数

查询元素

auto f = un_mp.find("test");
if (f != un_mp.end())
    std::cout << f -> second << '\n';

返回的是迭代器的指针 , 如果不存在的话返回 un_map.end()

修改元素

这里推荐的指正修改和 [] 运算符直接修改

  • 第一个指针修改
    auto f = un_mp.find("test");
    std::cout << f -> second << '\n';
    
  • 第二个 [] 运算符修改
    un_mp["test"] = 333;  通过key修改value
    

遍历集合

un_mp["s"] = 1;
un_mp["ss"] = 2;
un_mp["sss"] = 3;
un_mp["ssss"] = 4;

for (auto x : un_mp)
    std::cout << x.second << ' ';

防被卡hash

很多比赛都会针对unordered_mapunordered\_mapunordered_setunordered\_set 的底层hash值 , 来造数据 ,导致对于单次的查询为O(n)O(n) , 而不是 O(1)O(1)的 , 所以可能对于n个元素的插入,再查询可能是O(n2)O(n ^ 2) , 导致超时。 所以给出一种防被卡的方法

struct custom_hash {
    static LL splitmix64(LL x) {
        x ^= x << 13;
        x ^= x >> 7;
        x ^= x << 17;
        return x; 
    }
    size_t operator () (LL x) const {
        static const LL FIXED_RANDOM = std::chrono::steady_clock::now().time_since_epoch().count(); // 时间戳
        return splitmix64(x + FIXED_RANDOM);
    }
};

可以这么定义

std::unordered_map<LL, bool, custom_hash> safe_map;

注意: 对于hash函数如果是std::string作为keystd::string 作为key的话 , 只需要将字符串hash成数值,再进行以上操作 , 修改 重载的 ()LL---->std::string

补充std::multimapstd::multimap

插入元素(不支持 [] 运算符)

mp.insert(std::make_pair(2 , 1));

删除键对应一个值

// 匹配键的值
mp.erase(2);

删除指定键的元素

auto it = mp.find(2); // 删除迭代器指向的元素
if (it != mp.end()) { 
    mp.erase(it); 
}

图论

单源最短路 Dijkstra (顶点1为起点)

std::vector<PII> e[N];

void dijkstra(int s){
    
    std::vector<int> dist(n + 1 , INF);
    std::vector<bool> st(n + 1 , 0);
    
    for (int i = 1; i <= p; i ++ )
        dist[i] = INF , st[i] = 0;
    
    dist[s] = 0;
    std::priority_queue<PII , std::vector<PII> , std::greater<PII>> heap;
    heap.push({0 , s});             // 加入始点
    
    while (heap.size()){
        
        auto t = heap.top();
        heap.pop();
        
        int u = t.second;
        if (st[u]) continue;
        
        st[u] = true;
        
        for (auto p : e[u]){
            int v = p.first , w = p.second;
            
            if (dist[v] > dist[u] + w){
                dist[v] = dist[u] + w;
                heap.push({dist[v] , v});
            }
        }
    }
}

单源k短路(最多经过k条边)bellman-ford

起点为 1

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510, M = 10010;

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

int n, m, k;
int dist[N];
int last[N];

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

    dist[1] = 0;
    for (int i = 0; i < k; i ++ )
    {
        memcpy(last, dist, sizeof dist);
        for (int j = 0; j < m; j ++ )
        {
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], last[e.a] + e.c);
        }
    }
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);

    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        edges[i] = {a, b, c};
    }

    bellman_ford();

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

    return 0;
}

spfa 算法

struct edge {
    int v, w;
};

vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;

bool spfa(int n, int s) {
    memset(dis, 63, sizeof(dis));
    dis[s] = 0, vis[s] = 1;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        q.pop(), vis[u] = 0;
        for (auto ed : e[u]) {
            int v = ed.v, w = ed.w;
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                cnt[v] = cnt[u] + 1;  // 记录最短路经过的边数
                if (cnt[v] >= n) return false;
                // 在不经过负环的情况下,最短路至多经过 n - 1 条边
                // 因此如果经过了多于 n 条边,一定说明经过了负环
                if (!vis[v]) q.push(v), vis[v] = 1;
            }
        }
    }
    return true;
}

spfa 判负环

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>

const int N = 510 , INF = 0x3f3f3f3f;

#define PII std::pair<int , int>

int n , m , w;
std::vector<PII> e[N];

bool spfa(){
    
    std::vector<bool> st(n + 1);
    std::vector<int> num(n + 1);
    std::vector<int> dist(n + 1);
    std::queue<int> q;
   
    for (int i = 1; i <= n; i ++ ){
        q.push(i);
        st[i] = 1;
    }
   
    while (q.size()){
        int u = q.front();
        q.pop();
        
        st[u] = 0;
        
        for (auto p : e[u]){
            int v = p.first , w = p.second;
            
            if (dist[v] > dist[u] + w){
                dist[v] = dist[u] + w;
                num[v] = num[u] + 1;
                
                if (num[v] >= n) return true;
                
                if (!st[v]){
                    
                    q.push(v);
                    st[v] = 1;
                }
            }
        }
    }
    
    
    return false;
}

void solved(){
    
    for (int i = 1; i <= n; i ++ ) e[i].clear();
    
    std::cin >> n >> m >> w;
    for (int i = 0; i < m; i ++ ){
        int a , b , c;
        std::cin >> a >> b >> c;
        e[a].push_back({b , c});
        e[b].push_back({a , c});
    }
    
    for (int i = 0; i < w; i ++ ){
        int a , b , c;
        std::cin >> a >> b >> c;
        e[a].push_back({b , -c});
    }
    
    bool ok = spfa();
    
    std::cout << (ok ? "YES" : "NO") << '\n';
}


int main(){
    
    int tc = 1;
    std::cin >> tc;
    while (tc --)
        solved();
    
    return 0;
}

多源最短路 Floyd

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 210, INF = 1e9;

int n, m, Q;
int d[N][N];

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

int main()
{
    scanf("%d%d%d", &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;
        scanf("%d%d%d", &a, &b, &c);
        d[a][b] = min(d[a][b], c);
    }

    floyd();

    while (Q -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);

        int t = d[a][b];
        if (t > INF / 2) puts("impossible");        // 注意这个判无法联通 INF / 2
        else printf("%d\n", t);
    }

    return 0;
}

全源最短路 johnson 算法

给定一个包含 nn 个结点和 𝑚𝑚 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。

#include <cstring>
#include <iostream>
#include <queue>
#define INF 1e9
using namespace std;
struct edge {
  int v, w, next;
} e[10005];
struct node {
  int dis, id;
  bool operator<(const node& a) const { return dis > a.dis; }
  node(int d, int x) { dis = d, id = x; }
};
int head[5005], vis[5005], t[5005];
int cnt, n, m;
long long h[5005], dis[5005];
void addedge(int u, int v, int w) {
  e[++cnt].v = v;
  e[cnt].w = w;
  e[cnt].next = head[u];
  head[u] = cnt;
}
bool spfa(int s) {
  queue<int> q;
  memset(h, 63, sizeof(h));
  h[s] = 0, vis[s] = 1;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    vis[u] = 0;
    for (int i = head[u]; i; i = e[i].next) {
      int v = e[i].v;
      if (h[v] > h[u] + e[i].w) {
        h[v] = h[u] + e[i].w;
        if (!vis[v]) {
          vis[v] = 1;
          q.push(v);
          t[v]++;
          if (t[v] == n + 1) return false;
        }
      }
    }
  }
  return true;
}
void dijkstra(int s) {
  priority_queue<node> q;
  for (int i = 1; i <= n; i++) dis[i] = INF;
  memset(vis, 0, sizeof(vis));
  dis[s] = 0;
  q.push(node(0, s));
  while (!q.empty()) {
    int u = q.top().id;
    q.pop();
    if (vis[u]) continue;
    vis[u] = 1;
    for (int i = head[u]; i; i = e[i].next) {
      int v = e[i].v;
      if (dis[v] > dis[u] + e[i].w) {
        dis[v] = dis[u] + e[i].w;
        if (!vis[v]) q.push(node(dis[v], v));
      }
    }
  }
  return;
}
int main() {
  ios::sync_with_stdio(false);
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v, w;
    cin >> u >> v >> w;
    addedge(u, v, w);
  }
  for (int i = 1; i <= n; i++) addedge(0, i, 0);
  if (!spfa(0)) {
    cout << -1 << endl;
    return 0;
  }
  for (int u = 1; u <= n; u++)
    for (int i = head[u]; i; i = e[i].next) e[i].w += h[u] - h[e[i].v];
  for (int i = 1; i <= n; i++) {
    dijkstra(i);
    long long ans = 0;
    for (int j = 1; j <= n; j++) {
      if (dis[j] == INF)
        ans += j * INF;
      else
        ans += j * (dis[j] + h[j] - h[i]);
    }
    cout << ans << endl;
  }
  return 0;
}

开一个 pre 数组,在更新距离的时候记录下来后面的点是如何转移过去的,算法结束前再递归地输出路径即可。

比如 Floyd 就要记录 pre[i][j] = k;,Bellman–Ford 和 Dijkstra 一般记录 pre[v] = u

Prime 算法求最小生成树

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];
int dist[N];
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()
{
    scanf("%d%d", &n, &m);

    memset(g, 0x3f, sizeof g);

    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    int t = prim();

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

    return 0;
}

Kruskal算法求最小生成树

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

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

int n, m;
int p[N];

struct Edge
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

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

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

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    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()
{
    scanf("%d%d", &n, &m);

    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }

    int t = kruskal();

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

    return 0;
}

强连通分量 tarjan

强连通分量、缩点 参考例题 P2812 校园网络

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>

#define LL long long
#define PII std::pair<int , int>

const int N = 100010;

std::stack<int> stk;
std::vector<int> e[N];
int n , m , times , scc_cnt;
int id[N] , size[N];
int dfn[N] , low[N];
bool vis[N] , in_stack[N];
int in[N] , out[N];


void tarjan(int u ){

	// 入  盖戳
	dfn[u] = low[u] = ++ times;
	
	vis[u] = true;
	stk.push(u) , in_stack[u] = true;							// 加入栈中

	for (int v : e[u])
		if (!vis[v]){
			tarjan(v);											// 下
			low[u] = std::min(low[u] , low[v]);					// 回 , 子节点所能到的最低结点 , 父节点一定能到
		}else if (in_stack[v])					
			low[u] = std::min(low[u] , dfn[v]);						// 触 当前点再次搜索到 , 那么说明 v 是一个回溯点

	// 离	当前点是回溯点
	if (low[u] == dfn[u]){
		int y; ++ scc_cnt;
		do{
			y = stk.top();
			stk.pop();
			in_stack[y] = false;
			id[y] = scc_cnt;
			size[scc_cnt] ++;
		}while (y != u);
	}

}


int main(){

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

	std::cin >> n;
	for (int i = 1; i <= n; i ++ ){
		int x ;std::cin >> x;
		while (x != 0) {
			e[i].push_back(x);
			std::cin >> x;
		}
	}

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

	for (int i = 1; i <= n; i ++ )
		for (int v : e[i])
			if (id[i] != id[v])				// 不在同一个强连通分量中
				in[id[v]] ++ , out[id[i]] ++;

	int res_1 = 0 , res_2 = 0;
	for (int i = 1; i <= scc_cnt; i ++ ){
		if (in[i] == 0) res_1 ++ ;
		if (out[i] == 0) res_2 ++ ;
	}

	std::cout << res_1 << '\n';
	if (scc_cnt == 1)
		std::cout << 0 << '\n';
	else
		std::cout << std::max(res_1 , res_2) << '\n';


	return 0;
}

树链剖分

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

#define LL long long

const int N = 500010;

int n , m , s;
std::vector<int> e[N];

int size[N] , p[N] , dep[N] , son[N];
int top[N];

void dfs1 (int u , int fa){
    p[u] = fa , size[u] = 1 , dep[u] = dep[fa] + 1;

    for (auto v : e[u]){
        if (v == fa) continue;
        dfs1(v , u);
        size[u] += size[v];
        if (size[son[u]] < size[v]) son[u] = v;
    }

}

void dfs2 (int u , int t){

    top[u] = t;
    if (son[u] == 0) return ;

    dfs2(son[u] , t);

    for (int v : e[u]){
        if (v == p[u] || v == son[u]) continue;
        dfs2(v , v);
    }
}

int lca(int  u , int v){

    while (top[u] != top[v]){
        if (dep[top[u]] < dep[top[v]]) std::swap(u , v);
        u = p[top[u]];
    }

    return dep[u] < dep[v] ? u : v;
}


int main(){
    
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);


    std::cin >> n >> m >> s;
    for (int i = 1; i < n; i ++ ){
        int a , b;
        std::cin >> a >> b;
        e[a].push_back(b) , e[b].push_back(a);
    }

    dfs1(s , 0);
    dfs2(s , 0);

    while(m -- ){
        int u , v;
        std::cin >> u >> v;
        std::cout << lca(u , v) << '\n';
    }

    return 0;
}

tarjan 离线算法

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

#define LL long long
#define PII std::pair<int , int>

const int N = 500010;

int n , m , s;
std::vector<PII> q[N];
std::vector<int> e[N];
int fa[N] , ans[N];
bool vis[N];

int find (int u){

	if (fa[u] != u) fa[u] = find(fa[u]);

	return fa[u];
}

void tarjan (int  u){

	vis[u] = true;
	for (int v : e[u])
		if (!vis[v]){
			tarjan(v);
			fa[v] = u;
		}

	for (auto p : q[u]){ 
		int x = p.first , i = p.second;
		if (vis[x]) ans[i] = find(x);
	}

}


int main(){

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

	std::cin >> n >> m >> s;

	for (int i = 1; i <= n; i ++ ) fa[i] = i;

	for (int i = 1; i < n; i ++ ){
		int a , b;
		std::cin >> a >> b;
		e[a].push_back(b);
		e[b].push_back(a);
	}

	for (int i = 1; i <= m; i ++ ){
		int a , b;
		std::cin >> a >> b;
		q[b].push_back({a , i});
		q[a].push_back({b , i});
	}

	tarjan(s);

	for (int i = 1; i <= m; i ++ )
		std::cout << ans[i] << '\n';

	return 0;
}

二分图染色法

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 100010;

int n, m;
std::vector<int> e[N];
int color[N];

bool dfs(int u, int c)
{
    color[u] = c;

    for (int v : e[u])
    {
        if (!color[v])
        {
            if (!dfs(v , 3 - c)) return false;
        }
        else if (color[v] == c) return false;
    }

    return true;
}

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

    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        e[a].push_back(b);
        e[b].push_back(a);
    }

    bool flag = true;
    for (int i = 1; i <= n; i ++ )
        if (!color[i])
        {
            if (!dfs(i, 1))
            {
                flag = false;
                break;
            }
        }

    if (flag) puts("Yes");
    else puts("No");

    return 0;
}

二分图最大匹配

本质:寻找增广路

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>

const int N = 510 , INF = 2e9 + 7;

#define LL long long
#define PII std::pair<int , int>


int n , m , k;
int match[N];
bool vis[N];
std::vector<int> e[N];

bool dfs(int u){                // 枚举到这个点

    for (int v : e[u]){             // 枚举邻接点
        if (vis[v]) continue;
        vis[v] = 1;
        if (!match[v] || dfs(match[v])) {
            match[v] = u;
            return 1;
        }
    }

    return 0;
}


int main(){

    std::ios::sync_with_stdio(0);
    std::cout.tie(0);
    std::cin.tie(0);
    
    std::cin >> n >> m >> k;
    for (int i = 0; i < k; i ++ ){
        int a , b;
        std::cin >> a >> b;
        e[a].push_back(b);
    }

    int ans = 0;
    for (int i = 1; i <= n; i ++ ){
        for (int i = 1; i <= m; i ++ ) vis[i] = 0;
        if (dfs(i)) ans ++ ;
    }

    std::cout << ans << '\n';
    
    return 0;
}

拓扑排序

  1. top 排序只需要记录入度 宽搜
  2. 将入度为0的结点入队
  3. 从入度为0的结点开始,然后擦掉它的出边
  4. 将入度为的0点加入队列 ,输出该点

应用1. top 排序可用于判环,如果没有走完所有点说明有环。
应用2. top 排序可用于判唯一的关系,如果队列中出现两个及以上的元素个数说明top排序不唯一。

数论

筛法

埃式筛法

#include <iostream>
#include <algorithm>

using namespace std;

const int N= 1000010;

int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        primes[cnt ++ ] = i;
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}

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

    get_primes(n);

    cout << cnt << endl;

    return 0;
}

线性筛法

#include <iostream>
#include <algorithm>

using namespace std;

const int N= 1000010;

int primes[N], cnt;
bool st[N];

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;
        }
    }
}

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

    get_primes(n);

    cout << cnt << endl;

    return 0;
}

约数和

#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>

using namespace std;

typedef long long LL;

const int N = 110, mod = 1e9 + 7;

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)
    {
        LL a = p.first, b = p.second;
        LL t = 1;
        while (b -- ) t = (t * a + 1) % mod;
        res = res * t % mod;
    }

    cout << res << endl;

    return 0;
}

快速幂

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;


LL qmi(int a, int b, int p)
{
    LL res = 1 % p;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * (LL)a % p;
        b >>= 1;
    }
    return res;
}


int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, b, p;
        scanf("%d%d%d", &a, &b, &p);
        printf("%lld\n", qmi(a, b, p));
    }

    return 0;
}

快速幂求逆元

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;


LL qmi(int a, int b, int p)
{
    LL res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * (LL)a % p;
        b >>= 1;
    }
    return res;
}


int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, p;
        scanf("%d%d", &a, &p);
        if (a % p == 0) puts("impossible");
        else printf("%lld\n", qmi(a, p - 2, p));
    }

    return 0;
}

高精度快速幂

kpk^p 快速幂

const int N = 1100;

int p;
int a[N] , res[N] , c[N];

void result_1(){

    memset(c , 0 , sizeof c);

    for (int i = 1; i <= 500; i ++ )
        for (int j = 1; j <= 500; j ++ ) c[i + j - 1] += res[j] * a[i];

    for (int i = 1; i <= 500; i ++ )
        if(c[i] / 10){
            c[i + 1] += c[i] / 10;
            c[i] %= 10;
        }

    memcpy(res , c , sizeof c);
}

void result_2(){

    memset(c  , 0 , sizeof c);

    for (int i = 1; i <= 500; i ++ )
        for (int j = 1; j <= 500; j ++ ) c[i + j - 1] += a[i] * a[j];

    for (int i = 1; i <= 500; i ++ )
        if(c[i] / 10){
            c[i + 1] += c[i] / 10;
            c[i] %= 10;
        }

    memcpy(a , c, sizeof c);
}

void qmi(int p , int k){ 

    a[1] = k , res[1] = 1;

    while (p){
        if(p & 1) result_1();
        p >>= 1;
        result_2();
    }

}