零、前置

1. 头文件模板

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

// #define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define x first
#define y second
#define endl '\n'
// #define INF 0x3f3f3f3f3f3f3f3f
#define INF 0x3f3f3f3f

typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<string, int> PSI;

// int dx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, dy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

const int N = 2e5 + 5, MOD = 1e9 + 7;

LL mo(LL x)	{return (x % MOD + MOD) % MOD;}

void oper()
{
	
	return ;
}

int main()
{
	IOS
	
	int T = 1;
	// cin >> T;
	while (T --)	oper();
	
	return 0;
}

2. 解题思路

  1. 数据范围
    • n <= 30,指数级别:DFS + 剪枝,状压DP
    • n < 100,O(n ^ 3):Floyd,DP,高斯消元
    • n <= 1000,O(n ^ 2),O(n ^ 2 * log n):DP,二分,朴素Dijkstra,朴素Prim,Bellman-Ford
    • n <= 10000,O(n * n ^ 1/2):块状链表,分块,莫队
    • n <= 100000,O(n * logn):各种Sort,线段树,树状数组,Set/Map,Heap,拓扑排序,堆优化版Dijkstra,堆优化版Prim,Kruskal,Spfa,求凸包,求半平面交,二分,CDQ分治,整体二分,后缀数组,树链剖分,动态树
    • n <= 1000000,O(n),以及常数较小的O(n * logn):单调队列,Hash,双指针扫描,BFS,并查集,KMP,AC自动机,Sort,树状数组,Heap,Dijkstra,Spfa
    • n <= 10000000,O(n):双指针扫描,KMP,AC自动机,线性筛素数
    • n <= 1e9,O(n ^ 1/2):判断质数
    • n <= 1e18,O(logn):最大公约数,快速幂,数位DP
    • n <= 1e1000,O(logn ^ 2):高精度加减乘除
    • n <= 1e100000,O(logk * loglogk),k表示位数,高精度加减,FFT/NTT
  2. 特殊情况
    • 直接能 cout << 答案的情况
  3. 初始化情况
    • 多组测试数据需要每次对相关变量置 0
  4. 边界情况
    • 数组边界:a[0],a[n]
    • 对STL操作前判断非空
  5. 输出格式
    • 行末空格
    • Yes、YES 与 yes 的区别
  6. 优化
    • 快读
    • endl 替换为 ‘\n’
    • for (auto &item : v) 取地址加快

一、基础算法

1. 前缀和

(1)一维前缀和

for (int i = 1; i <= n; i ++)	prefix[i] = prefix[i - 1] + a[i];
cout << prefix[r] - prefix[l - 1] << endl;

(2)二维前缀和

// 初始化
for (int i = 1; i <= n; i ++)
	for (int j = 1; j <= m; j ++)
		prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1] + a[i][j];

// 求区域和
cout << prefix[x2][y2] - prefix[x2][y1 - 1] - prefix[x1 - 1][y2] + prefix[x1 - 1][y1 - 1] << endl;

// Tips:前缀和可能需要开 LL

2. 差分

(1)一维差分

// 初始化
for (int i = 1; i <= n; i ++)	diff[i] = a[i] - a[i - 1];

// 修改 [l, r] 区间
diff[l] += x, diff[r + 1] -= x;

(2)二维差分

// 修改 (x1, y1, x2, y2) 区域
void modify(int x1, int y1, int x2, int y2, int c)
{
	diff[x1][y1] += c;
    diff[x2 + 1][y1] -= c;
    diff[x1][y2 + 1] -= c;
    diff[x2 + 1][y1 + 1] += c;
}

// 初始化
for (int i = 1; i <= n; i ++)
    for (int j = 1; j <= m; j ++)
        modify(i, j, i, j, a[i][j]);

// 修改区域
cin >> x1 >> y1 >> x2 >> y2 >> x;
	modify(x1, y1, x2, y2, x);

// 后续前缀和处理

3. 二分

(1)整数二分

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

// 情况二:
while (l < r)
{
	int mid = l + r + 1 >> 1;
	if (check(mid))		l = mid;
	eles 				r = mid - 1;
}

// Tips:l = mid 时 l + r 要 + 1

(2)浮点数二分

double l = -100, r = 100;
while (r - l > 1e8)
{
	double mid = (l + r) / 2;
	if (check(mid))		r = mid;
	else 				l = mid;
}

4. 位运算

// 与、或、非、位移、异或、取反

// lowbit:返回二进制中最后一个 1 的位置,用于树状数组
int lowbit(int x)
{
	return x & -x;
}

// 相关推论:
1. 异或:(1)结合律、交换律(无分配率)
    	(2)a ^ 0 = a, a ^ a = 0
2. a + b = 2 * (a & b) + (a ^ b)  =>  a + b >= a ^ b;

5. 排序

(1)sort排序

// 静态数组排序
sort(a, a + n);

// 动态数组排序
vector<int> v;
sort(v.begin(), v.end());

// 结构体排序
struct Book
{
	int a, b, c;

	bool operator < (const Book &E) const
	{
		if (a == E.a && b == E.b)	return c > E.c;
		else if (a == E.a)			return b > E.b;
		else 						return a > E.a;
	}
}p[N];
sort(p, p + n);

// 排序去重
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());

(2)桶排序

cin >> n;

int x;
for (int i = 0; i < n; i ++)
{
    cin >> x;
    cnt[x] ++;
}

for (int i = 0; i < 2e5 + 5; i ++)
{
    while (cnt[i])
    {
        cout << i << ' ';
		cnt[i] --;
    }
}

6. 并查集

(1)朴素并查集(带路径压缩)

int find(int x) {return p[x] = p[x] == x ? x : find(p[x]);}

void merge(int a, int b)
{
    p[find(a)] = find(b);
}

(2)可撤销并查集(空)

7. 高精度

// 高精度加法
vector<int> add(vector<int> A, vector<int> B)
{
	vector<int> ans;
	for (int i = 0, t = 0; i < A.size() || i < B.size() || t; i ++)
	{
		if (i < A.size())	t += A[i];
		if (i < B.size())	t += B[i];
		ans.push_back(t % 10);
		t /= 10;
	}
	return ans;
}

// 高精度减法
bool cmp(vector<int> A, vector<int> B)
{
	if (A.size() != B.size())	return A.size() > B.size();
	else
	{
		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> ans;
	for (int i = 0, t = 0; i < A.size(); i ++)
	{
		t = A[i] - t;
		if (i < B.size())	t -= B[i];
		ans.push_back((t + 10) % 10);
		if (t < 0)	t = 1;
		else t = 0;
	}

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

// 高精度乘法
vector<int> mul(vector<int> A, int b)
{
	vector<int> ans;
	for (int i = 0, t = 0; i < A.size() || t; i ++)
	{
		if (i < A.size())	t += A[i] * b;
		ans.push_back(t % 10);
		t /= 10;
	}

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

// 高精度除法
vector<int> div(vector<int> A, int b, int &r)
{
	vector<int> ans;
	r = 0;
	for (int i = A.size() - 1; i >= 0; i --)
	{
		r = r * 10 + A[i];
		ans.push_back(r / b);
		r %= b;
	}

	reverse(ans.begin(), ans.end());
	while (ans.back() == 0 && ans.size() > 1)	ans.pop_back();
	return ans;
}

// 初始化
string a;
vector<int> A;
for (int i = a.size() - 1; i >= 0; i --)	A.push_back(a[i] - '0');
for (int i = ans.size() - 1; i >= 0; i --)	cout << ans[i];

二、数据结构与STL

1. 栈、队列

queue<int> q;
q.pop();
q.front();
q.push();
q.size();
q.back()
q.empty();

stack<int> stk;
stk.push();
stk.top();
stk.pop();
stk.size();
stk.empty();

// Tips:queue,stack 无 clear 函数

2. 双端队列 Deque

deque<int> dq;

dq.size(); 			
dq.empty(); 		
dq.clear(); 		// 清空这个双端队列
dq.front(); 		// 返回第一个元素
dq.back(); 			// 返回最后一个元素
dq.push_back(); 	// 向最后插入一个元素
dq.pop_back(); 		// 弹出最后一个元素
dq.push_front(); 	// 向队首插入一个元素
dq.pop_front(); 	// 弹出第一个元素
dq.begin(); 		// 双端队列的第0个数
dq.end(); 			// 双端队列的最后一个的数的后面一个数

3. 优先队列(堆)

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

heap.top();
heap.pop();
heap.push();
heap.size();
heap.empty();

4. Map

map<int, int> mp;
unordered_map<int, int> mp;

map.size();
map.empty();
map.clear();
map.begin();
map.end();  			// 支持++,--操作,返回前驱和后继
map.insert();  			// 插入的是一个pair
map.erase();  			// 输入的参数是pair或者迭代器
map.find();  
map.lower_bound(x);		// 返回大于等于x的最小的数的迭代器
map.upper_bound(x);		// 返回大于x的最小的数的迭代器

5. Set

set<int> hash;
unordered_set<int> hash;

set.size();
set.empty();
set.clear();
set.begin();
set.end();  			//支持++,--操作,返回前驱和后继
set.insert();  			//插入一个数
set.find();  			//查找一个数,若不存在返回end迭代器
set.count();  			//返回某一个数的个数
set.erase(); 	 		//删除操作
set.erase(x);			//输入一个数,删除所有x; O(k+logn);
set.erase(迭代器);		  //输入一个迭代器,删除这个迭代器
set.lower_bound(x);  	//返回大于等于x的最小的数的迭代器
set.upper_bound(x);  	//返回大于x的最小的数的迭代器

6. bitset

bitset<10000> vis;	

vis.reset();		// 将所有位置为 0
vis.count();		// 返回 1 的个数
vis.any();			// 判断是否至少有一个 1
vis.none();			// 判断是否全为 0

// Tips:直接当做 bool 数组使用

7. 单调栈

// 表示每个元素的左边第一个比它小的元素
for (int i = 1; i <= n; i ++)
{
    // 判断栈顶
    while (stk.size() && stk.top() >= a[i])	stk.pop();
    // 输出答案
    if (stk.size())		cout << stk.top() << ' ';
    else 				cout << -1 << ' ';
    // 入栈
    stk.push(a[i]);
}

8. 单调队列

for (int i = 1; i <= n; i ++)
{
    // 队头处理
    while (dq.size() && i - dq.front() + 1 > k)		dq.pop_front();
	// 队尾处理
    while (dq.size() && a[dq.back()] < a[i])		dq.pop_back();
	// 入队
    dq.push_back(i);
	// 输出答案
    if (i >= k)	cout << (dq.size() ? a[dq.front()] : -1) << " \n"[i == n];
}

9. 树状数组

// 单点修改
void update(int k, int x)
{
	for (int i = k; i <= n; i += lowbit(i))
		tr[i] += x;
}

// 区间查询
LL query(int x)
{
	LL res = 0;
	for (int i = x; i >= 1; i -= lowbit(i))
		res += tr[i];
	return res;
}

10. 朴素线段树

(1)加法线段树

LL a[N], tr[N * 4], lazy[N * 4];				// lazy 数组表示 u 当前节点还有任务需要下放
// 操作 u 号节点
void update(int st, int ed, int u, LL x)
{
	lazy[u] += x;
	tr[u] += (ed - st + 1) * x * 1LL;
}
// pushup 向上维护
void pushup(int u)
{
	tr[u] = tr[u << 1] + tr[u << 1 | 1];
}
// pushdown 向下更新
void pushdown(int st, int ed, int u)
{
	if (lazy[u] == 0)		return ;			// 当前节点没有 lazy 下放
	int mid = (st + ed) >> 1;
	// 更新子节点的 lazy 和 tr
	update(st, mid, u << 1, lazy[u]);			
	update(mid + 1, ed, u << 1 | 1, lazy[u]);
	lazy[u] = 0;								// 下放完毕
}
// 建树
void buildTree(int st = 1, int ed = n, int u = 1)
{
	if (st == ed)								// 叶子节点
	{
		tr[u] = a[st];
		return ;
	}
	int mid = (st + ed) >> 1;
	// 左右子树
	buildTree(st, mid, u << 1);
	buildTree(mid + 1, ed, u << 1 | 1);
	pushup(u);									// 左右子树构建完后要 pushup 更新父节点
}
// 区间修改
void modify(int l, int r, int u, int st, int ed, LL x)
{
	if (st >= l && ed <= r)						// 当前管辖区间完全包含于 [l, r]
	{
		update(st, ed, u, x);					
		return ;
	}
	pushdown(st, ed, u);						// 向下走就要 pushdown
	int mid = (st + ed) >> 1;
	if (mid >= l)		modify(l, r, u << 1, st, mid, x);
	if (mid + 1 <= r)	modify(l, r, u << 1 | 1, mid + 1, ed, x);
	pushup(u);									// 修改完子树就要 pushup
}
// 区间查询
LL query(int l, int r, int u, int st, int ed)
{
	if (st >= l && ed <= r)		return tr[u];	// 当前管辖区间完全包含于 [l, r]
	pushdown(st, ed, u);						// 向下走就要 pushdown
	// 求子树之和
	LL ans = 0;
	int mid = (st + ed) >> 1;
	if (mid >= l)		ans += query(l, r, u << 1, st, mid);
	if (mid + 1 <= r)	ans += query(l, r, u << 1 | 1, mid + 1, ed);
	return ans;
}
void oper()
{
	cin >> n >> q;
	for (int i = 1; i <= n; i ++)	cin >> a[i];

	buildTree();								// 建树

	int op, l, r;
	LL x;
	while (q --)
	{
		cin >> op;
		if (op == 1)	
		{
			cin >> l >> r >> x;
			modify(l, r, 1, 1, n, x);
		}
		else			
		{
			cin >> l >> r;
			cout << query(l, r, 1, 1, n) << endl;
		}
	}
}

(2)最值线段树

LL a[N], trmax[N * 4], trmin[N * 4], lazy[N * 4];				// lazy 数组表示 u 当前节点还有任务需要下放
// 操作 u 号节点
void update(int st, int ed, int u, LL x)
{
	lazy[u] += x;
	trmax[u] += x;
	trmin[u] += x;
}
// pushup 向上维护
void pushup(int u)
{
	trmax[u] = max(trmax[u << 1], trmax[u << 1 | 1]);
	trmin[u] = min(trmin[u << 1], trmin[u << 1 | 1]);
}
// pushdown 向下更新
void pushdown(int st, int ed, int u)
{
	if (lazy[u] == 0)		return ;			// 当前节点没有 lazy 下放
	int mid = (st + ed) >> 1;
	// 更新子节点的 lazy 和 tr
	update(st, mid, u << 1, lazy[u]);			
	update(mid + 1, ed, u << 1 | 1, lazy[u]);
	lazy[u] = 0;								// 下放完毕
}
// 建树
void buildTree(int st = 1, int ed = n, int u = 1)
{
	if (st == ed)								// 叶子节点
	{
		trmin[u] = trmax[u] = a[st];
		return ;
	}
	int mid = (st + ed) >> 1;
	// 左右子树
	buildTree(st, mid, u << 1);
	buildTree(mid + 1, ed, u << 1 | 1);
	pushup(u);									// 左右子树构建完后要 pushup 更新父节点
}
// 区间修改
void modify(int l, int r, int u, int st, int ed, LL x)
{
	if (st >= l && ed <= r)						// 当前管辖区间完全包含于 [l, r]
	{
		update(st, ed, u, x);					
		return ;
	}
	pushdown(st, ed, u);						// 向下走就要 pushdown
	int mid = (st + ed) >> 1;
	if (mid >= l)		modify(l, r, u << 1, st, mid, x);
	if (mid + 1 <= r)	modify(l, r, u << 1 | 1, mid + 1, ed, x);
	pushup(u);									// 修改完子树就要 pushup
}
// 区间查询
LL queryMax(int l, int r, int u, int st, int ed)
{
	if (st >= l && ed <= r)		return trmax[u];	// 当前管辖区间完全包含于 [l, r]
	pushdown(st, ed, u);						// 向下走就要 pushdown
	// 求子树之和
	LL ans = -INF;
	int mid = (st + ed) >> 1;
	if (mid >= l)		ans = max(ans, queryMax(l, r, u << 1, st, mid));
	if (mid + 1 <= r)	ans = max(ans, queryMax(l, r, u << 1 | 1, mid + 1, ed));
	return ans;
}
// 区间查询
LL queryMin(int l, int r, int u, int st, int ed)
{
	if (st >= l && ed <= r)		return trmin[u];	// 当前管辖区间完全包含于 [l, r]
	pushdown(st, ed, u);						// 向下走就要 pushdown
	// 求子树之和
	LL ans = INF;
	int mid = (st + ed) >> 1;
	if (mid >= l)		ans = min(ans, queryMin(l, r, u << 1, st, mid));
	if (mid + 1 <= r)	ans = min(ans, queryMin(l, r, u << 1 | 1, mid + 1, ed));
	return ans;
}

void oper()
{
	cin >> n >> q;
	for (int i = 1; i <= n; i ++)	cin >> a[i];

	buildTree();								// 建树

	int op, l, r;
	LL x;
	while (q --)
	{
		cin >> op;
		if (op == 1)	
		{
			cin >> l >> r >> x;
			modify(l, r, 1, 1, n, x);
		}
		else			
		{
			cin >> l >> r;
			cout << queryMax(l, r, 1, 1, n) << ' ' << queryMin(l, r, 1, 1, n) << endl;
		}
	}
}

(3)异或线段树

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

// #define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define x first
#define y second
#define endl '\n'
// #define INF 0x3f3f3f3f3f3f3f3f
#define INF 0x3f3f3f3f

typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<string, int> PSI;

// int dx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, dy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

const int N = 2e5 + 5, MOD = 1e9 + 7;

LL mo(LL x)	{return (x % MOD + MOD) % MOD;}

int n, q;
LL a[N], tr[N * 4], lazy[N * 4];

void update(int u, int st, int ed, LL x)
{
	tr[u] ^= ((ed - st + 1) & 1) ? x : 0;
	lazy[u] ^= x;
}

void pushdown(int u, int st, int ed)
{
	if (lazy[u] == 0)	return ;
	
	int mid = st + ed >> 1;
	update(u << 1, st, mid, lazy[u]);
	update(u << 1 | 1, mid + 1, ed, lazy[u]);
	lazy[u] = 0;
}

void pushup(int u)
{
	tr[u] = tr[u << 1] ^ tr[u << 1 | 1];
}

void buildTree(int u, int st, int ed)
{
	if (st == ed)
	{
		tr[u] = a[st];
		return ;
	}
	
	int mid = st + ed >> 1;
	buildTree(u << 1, st, mid);
	buildTree(u << 1 | 1, mid + 1, ed);
	pushup(u);
}

void modify(int u, int st, int ed, int l, int r, LL x)
{
	if (l <= st && ed <= r)	
	{
		update(u, st, ed, x);
		return ;
	}
	pushdown(u, st, ed);
	int mid = st + ed >> 1;
	if (mid >= l)		modify(u << 1, st, mid, l, r, x);
	if (mid + 1 <= r)	modify(u << 1 | 1, mid + 1, ed, l, r, x);
	pushup(u);
}

LL query(int u, int st, int ed, int l, int r)
{
	LL res = 0;
	if (l <= st && ed <= r)		return tr[u];
	pushdown(u, st, ed);
	int mid = st + ed >> 1;
	if (mid >= l)		res ^= query(u << 1, st, mid, l, r);
	if (mid + 1 <= r)	res ^= query(u << 1 | 1, mid + 1, ed, l, r);
	
	return res;
}

void oper()
{
	cin >> n >> q;
	for (int i = 1; i <= n; i ++)	cin >> a[i];
	
	buildTree(1, 1, n);
	
	int op, l, r;
	LL x;
	while (q --)
	{
		cin >> op >> l >> r;
		if (op == 1)				// 异或
		{
			cin >> x;
			modify(1, 1, n, l, r, x);
		}
		else						// 求和
		{
			cout << query(1, 1, n, l, r) << endl;
		}
	}	
	return ;
}

int main()
{
	IOS
	
	int T = 1;
	// cin >> T;
	while (T --)	oper();
	
	return 0;
}

(4)线段树进阶(加法,乘法,赋值,求和)

// 取模函数
LL mo(LL x)	{return (x % MOD + MOD) % MOD;}	

int n, q;
LL a[N], tr[N * 4], mul[N * 4], add[N * 4];			// 定义操作为: tr * k + x

void update(int u, int st, int ed, LL k, LL x)
{
	// (mul[u] * y + add[u]) * k + x  ===>	(mul[u] * k) * y + (add[u] * k + x)
	tr[u] = mo(mo(tr[u] * k) + mo(x * (ed - st + 1)));
	mul[u] = mo(mul[u] * k);
	add[u] = mo(mo(add[u] * k) + x);
}
// 向上维护
void pushup(int u)
{
	tr[u] = mo(tr[u << 1] + tr[u << 1 | 1]);
}
// 向下分发
void pushdown(int u, int st, int ed)
{
	int mid = st + ed >> 1;
	update(u << 1, st, mid, mul[u], add[u]);
	update(u << 1 | 1, mid + 1, ed, mul[u], add[u]);
	
	mul[u] = 1, add[u] = 0;
}
// 建树
void buildTree(int u, int st, int ed)
{
	mul[u] = 1;										// 初始化 mul 数组
	if (st == ed)
	{
		tr[u] = a[st];
		return;
	}
	
	int mid = st + ed >> 1;
	buildTree(u << 1, st, mid);
	buildTree(u << 1 | 1, mid + 1, ed);
	pushup(u);
}
// 修改
void modify(int u, int st, int ed, int l, int r, LL k, LL x)
{
	if (l <= st && ed <= r)
	{
		update(u, st, ed, k, x);
		return ;
	}
	
	pushdown(u, st, ed);
	int mid = st + ed >> 1;
	if (mid >= l)		modify(u << 1, st, mid, l, r, k, x);
	if (mid + 1 <= r)	modify(u << 1 | 1, mid + 1, ed, l, r, k, x);
	pushup(u);
}
// 查询
LL query(int u, int st, int ed, int l, int r)
{
	LL res = 0;
	if (l <= st && ed <= r)		return tr[u];

	pushdown(u, st, ed);
	int mid = st + ed >> 1;
	if (mid >= l)		res = mo(res + query(u << 1, st, mid, l, r));
	if (mid + 1 <= r)	res = mo(res + query(u << 1 | 1, mid + 1, ed, l, r));
	return res;
}

void oper()
{
	cin >> n >> q;
	
	for (int i = 1; i <= n; i ++)	cin >> a[i];
	
	buildTree(1, 1, n);
	
	int op, l, r;
	LL x;
	while (q --)
	{
		cin >> op >> l >> r;
		if (op == 1)		// 加法
		{
			cin >> x;	
			modify(1, 1, n, l, r, 1, x);
		}
		else if (op == 2)	// 乘法
		{
			cin >> x;
			modify(1, 1, n, l, r, x, 0);
		}
		else if (op == 3)	// 赋值
		{
			cin >> x;
			modify(1, 1, n, l, r, 0, x);
		}
		else				// 求和 
		{
			cout << query(1, 1, n, l, r) << endl;
		}
	}
	return ;
}

11. 权值线段树

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

// #define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define x first
#define y second
#define endl '\n'
// #define INF 0x3f3f3f3f3f3f3f3f
#define INF 0x3f3f3f3f

typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<string, int> PSI;

// int dx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, dy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

const int N = 2e5 + 5, MOD = 1e9 + 7;

LL mo(LL x)	{return (x % MOD + MOD) % MOD;}

int q, tr[N * 4];		// 叶子节点tr[]存放的是个数

void pushup(int u)
{
	tr[u] = tr[u << 1] + tr[u << 1 | 1];
}
// 单点插入
void insert(int u, int st, int ed, int x)
{
	if (st == ed)	
	{
		tr[u] ++;
		return ;
	}
	
	int mid = st + ed >> 1;
	if (x <= mid)	insert(u << 1, st, mid, x);
	else			insert(u << 1 | 1, mid + 1, ed, x);
	pushup(u);
}
// 查询第 k 小(第 k 大)
int queryMin(int u, int st, int ed, int k)
{
	if (st == ed)		return st;
	
	int left_sum = tr[u << 1], mid = st + ed >> 1;
	if (k <= left_sum)	return queryMin(u << 1, st, mid, k);
	else				return queryMin(u << 1 | 1, mid + 1, ed, k - left_sum);
}
// 查询区间内数的个数
int queryCnt(int u, int st, int ed, int l, int r)
{
	if (l <= st && ed <= r)		return tr[u];
	
	int res = 0, mid = st + ed >> 1;
	if (mid >= l)		res += queryCnt(u << 1, st, mid, l, r);
	if (mid + 1 <= r)	res += queryCnt(u << 1 | 1, mid + 1, ed, l, r);
	return res;
}

void oper()
{
	cin >> q;
	int n = N;
	int op, l, r, x;
	while (q --)
	{
		cin >> op;
		if (op == 1)				// 插入元素
		{
			cin >> x;
			insert(1, 1, n, x);
		}
		else if (op == 2)			// 查询个数
		{
			cin >> l >> r;
			cout << queryCnt(1, 1, n, l, r) << endl;
		}
		else						// 第 k 小
		{
			cin >> x;
			cout << queryMin(1, 1, n, x) << endl;
		}
	}
	return ;
}

int main()
{
	IOS
	
	int T = 1;
	// cin >> T;
	while (T --)	oper();
	
	return 0;
}

12. 主席树(可持久化权值线段树)

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

// #define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define x first
#define y second
#define endl '\n'
// #define INF 0x3f3f3f3f3f3f3f3f
#define INF 0x3f3f3f3f

typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<string, int> PSI;

// int dx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, dy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

const int N = 2e5 + 5, MOD = 1e9 + 7;

LL mo(LL x)	{return (x % MOD + MOD) % MOD;}

vector<int> X;
LL bin(LL x) {return lower_bound(X.begin(), X.end(), x) - X.begin() + 1;};

struct Node
{
	int ls, rs, val;
}tr[N << 5];

int n, q, idx, a[N], rt[N];

void insert(int st, int ed, int &u, int pre, int x)
{
	u = ++ idx;
	tr[u] = tr[pre];
	tr[u].val ++;

	if (st == ed)	return ;
	
	int mid = st + ed >> 1;
	if (x <= mid)	insert(st, mid, tr[u].ls, tr[pre].ls, x);
	else			insert(mid + 1, ed, tr[u].rs, tr[pre].rs, x);
}

LL queryVal(int st, int ed, int lu, int ru, int k)
{
	if (st == ed)		return st;
	
	int mid = st + ed >> 1;
	LL left_sum = tr[tr[ru].ls].val - tr[tr[lu].ls].val;
	if (k <= left_sum)		return queryVal(st, mid, tr[lu].ls, tr[ru].ls, k);
	else					return queryVal(mid + 1, ed, tr[lu].rs, tr[ru].rs, k - left_sum);
	
}

void oper()
{
	cin >> n >> q;
	
	for (int i = 1; i <= n; i ++)	cin >> a[i];
	// 离散化
	for (int i = 1; i <= n; i ++)	X.push_back(a[i]);
	sort(X.begin(), X.end());
	X.erase(unique(X.begin(), X.end()), X.end());
	
	for (int i = 1; i <= n; i ++)	insert(1, n, rt[i], rt[i - 1], bin(a[i]));
	
	int l, r, k;
	while (q --)
	{
		cin >> l >> r >> k;
		cout << X[queryVal(1, n, rt[l - 1], rt[r], k) - 1] << endl;
	}
	
	return ;
}

int main()
{
	IOS
	
	int T = 1;
	// cin >> T;
	while (T --)	oper();
	
	return 0;
}

13. 01Trie树(空)

三、搜索与图论

1. DFS

void dfs(int u, int b, int c)	// 相关参数
{
	// 退出条件
	if (check())
    {
        ans ++;		// 记录答案
        return ;
	}
	// 改变状态
	st[u] = true;
	// 递归下一层
	dfs(u + 1);
	// 恢复现场
    st[u] = false;
}

2. BFS

void bfs()
{
	queue<int> q;
	q.push(a);	// 起点入队
	
	// 判断队空
	while (q.size())
	{	
        // 队头出队
		auto t = q.front();
		q.pop();
		
		// 处理队头
		check();
        
		// 新元素入队
		q.push(x);
	}
	
	cout << ans << endl;
}

3. 最短路

(1)单源最短路(堆优化版 Dijkstra)

struct Edge
{
	int a, w;
	bool operator < (const Edge &E) const
	{
		return w > E.w;
	}
};

int n, m, dist[N];
vector<Edge> v[N];
bitset<N> vis;

void dijkstra()
{
	memset(dist, 0x3f, sizeof dist);	// 初始化 dist 数组
	dist[1] = 0;						// 初始化起点

	priority_queue<Edge> heap;
	heap.push({1, dist[1]});			// 点和到点的距离

	while (heap.size())
	{
		auto tt = heap.top();
		int a = tt.a;
		int w = tt.w;
		heap.pop();

		if (vis[a])	continue;			// 冗余
		vis[a] = true;

		for (auto item : v[a])
		{
			int j = item.a;
			int w = item.w;

			if (dist[j] > dist[a] + w)
			{
				dist[j] = dist[a] + w;
				heap.push({j, dist[j]});
			}
		}
	}

	if (dist[n] >= INF / 2)		cout << -1 << endl;
	else 						cout << dist[n] << endl;
}

// 初始化
void oper()
{
	cin >> n >> m;

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

	dijkstra();
}

(2)多源最短路(Floyd)

for (int k = 1; k <= n; k ++)
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= n; j ++)
			g[i][j] = min(g[i][j], g[i][k] + g[k][j]);

// 初始化
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i ++)	g[i][i] = 0;
for (int i = 0; i < m; i ++)
{
	cin >> a >> b >> c;
    g[a][b] = min(g[a][b], c);	// 取最短边
}

// Tips:三层循环 k 在最外层

(3)带负权的单源最短路(BellmanFord)

(4)SPFA(队列优化版BellmanFord)

(5)带负权的全源最短路(Johnson)

4. 拓扑排序

vector<int> v[N];	// 存图
int d[N];			// 入度
vector<int> path;	// 方案路径

void topsort()
{
	queue<int> q;	

	// 入度为 0 的点入队
	for (int i = 1; i <= n; i ++)
		if (!d[i])
			q.push(i);

	while (q.size())
	{
		auto t = q.front();
		q.pop();
		path.push_back(t);					// 记录路径
		for (auto item : v[t])
		{
			d[item] --;
			if (!d[item])	q.push(item);	// 入度为 0 的点入队
		}
	}
	
    // 判断答案合法性
	if (path.size() == n)
		for (auto item : path)	
			cout << item << ' ';
	else 
		cout << -1 << endl;	
}

5. 最小生成树(Kruskal)

struct Edge
{
	int a, b;
	LL w;

	bool operator < (const Edge &E) const
	{
		return w < E.w;
	}
};

vector<Edge> v;
int p[N];

int find(int x)	{return p[x] = p[x] == x ? x : find(p[x]);}

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

	sort(v.begin(), v.end());					// 对边排序

	int cnt = 1;	
	LL ans = 0;									
	for (int i = 0; i < v.size(); i ++)
	{
		auto t = v[i];
	
		int a = t.a, b = t.b;
		LL w = t.w;
		
		if (find(a) != find(b))
		{
			p[find(a)] = find(b);
			cnt ++;
			ans += w;
		}
	}

	if (cnt == n)	cout << ans << endl;
	else 			cout << -1 << endl;
}

6. LCA

int depth[N], fa[N][30];					// fa[i][j] 表示 i 号节点向上走 2 ^ j 格
vector<int> g[N];

// 初始化 fa 与 depth 数组
void dfs(int u)
{
	depth[u] = depth[fa[u][0]] + 1;			// 记录当前深度
	for (int i = 1; i <= 20; i ++)	fa[u][i] = fa[fa[u][i - 1]][i - 1];
	// 遍历儿子节点
	for (auto &item : g[u])		dfs(item);
}
// 求 lca
int lca(int x, int y)
{
	if (depth[x] <= depth[y])	swap(x, y);	// 保持 x 在下方
	// 若 x 向上跳,挑了还比 y 更深,则跳
	for (int i = 20; i >= 0; i --)
		if (depth[fa[x][i]] >= depth[y])
			x = fa[x][i];
	if (x == y)		return x;				// x 与 y 在同一层
	for (int i = 20; i >= 0; i --)
	{
		if (fa[x][i] == fa[y][i])	continue;
		x = fa[x][i];
		y = fa[y][i];
        
	}
	return fa[x][0];
}

void oper()
{
	cin >> n;
	for (int i = 2; i <= n; i ++)	
	{
		cin >> fa[i][0];
		g[fa[i][0]].push_back(i);			// 父节点 -> i 连边
	}

	dfs(1);

	cin >> q;
	int x, y;
	while (q --)
	{
		cin >> x >> y;
		cout << lca(x, y) << endl;
	}
}

// 推论:
// (1) u -> v 的简单路径 = 两条链:u -> LCA(u, v) 和 LCA(u, v) -> v
// (2) LCA(x, y, z) = LCA(x, LCA(y, z)) = LCA(LCA(x, y), z) 类似 max, min, gcd
// (3) LCA(x1, x2, x3, x4 ... xk) = LCA(dfs序最小点, dfs序最大点)

7. Tarjan(空)

(1)Tarjan求LCA(空)

(2)Tarjan找环(空)

(3)Tarjan求有向图缩点(空)

(4)Tarjan求无向图求割点、割边(空)

四、数论

1. 试除法

bool is_prime(int n)
{
    if (n < 2)  return false;
    for (int i = 2; i <= n / i; i ++)
        if (n % i == 0)
            return false;
    return true;
}

2. 埃氏筛

LL n;
bitset<N> vis;					// vis = 1 表示为合数,vis = 0 表示为质数

void oper()
{
	cin >> n;

	vis[0] = vis[1] = 1;		// 将合数标记为1
	for (LL i = 2; i <= n; i ++)
		if (!vis[i])			// 若当前为质数,则将范围内所有质数的倍数标记为合数
			for (LL j = i * 2; j <= n; j += i)
				vis[j] = 1;

	for (LL i = 1; i <= n; i ++)
		if (!vis[i])	
			cout << i << ' ';
}

3. gcd 与 lcm

LL a, b;

void oper()
{
	cin >> a >> b;
	cout << __gcd(a, b) << ' ';             // 最大公约数
    cout << a * b / __gcd(a, b) << endl;    // 最小公倍数
}

4. 快速幂与逆元

LL qmi(LL a, LL k, LL p)    // 求 a ^ k % p
{
    LL res = 1;
    while (k)
    {
        if (k & 1)	res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 逆元 
auto t = qmi(a, p - 2, p);
if (a % p != 0)		cout << t << endl;
else 				cout << "impossible" << endl;

5. 组合数(进阶)

const int MOD = 1e9 + 7;
LL fact[N], infact[N];
LL qmi(LL a, LL k, LL p)    // 快速幂
{
	LL res = 1;
	while (k)
	{
		if (k & 1)	res = res * a % p;
		k >>= 1;
		a = a * a % p;
	}
	return res;
}

LL C(int n, int m)  // 求 C(n, m) = n! / [(n - m) ! * m!] 即 fact[n] * infact[n - m] % MOD * infact[m] % MOD
{
	return fact[n] * infact[m] % MOD * infact[n - m] % MOD;
}

void init(int n)    // 初始化 fact 与 infact 数组
{
	fact[0] = 1;
	for (int i = 1; i <= n; i ++) 	fact[i] = (LL)fact[i - 1] * i % MOD;

	infact[n] = qmi(fact[n], MOD - 2, MOD);
	for (int i = n - 1; i >= 0; i --)	infact[i] = (LL)infact[i + 1] * (i + 1) % MOD; 
}

五、动态规划

1. 背包问题

(1)01背包

for(int i = 1; i <= n; i ++)
	for (int j = m; j >= v[i]; j --)
		f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;

(2)完全背包

for(int i = 1; i <= n; i ++)
	for (int j = v[i]; j <= m; j ++)
		f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;

(3)多重背包(二进制优化)

int a, b, s, cnt = 0;					// 每种物品的体积,价格,数量,二进制打包后总物品种数
for (int i = 0; i < n; i ++)
{
	cin >> a >> b >> s;
	
	int k = 1;
	while (s >= k)
	{
		++ cnt;							// 先自增,再使用
		v[cnt] = k * a;
		w[cnt] = k * b;	
		s -= k;
		k *= 2;
	}
	
	if (s)
	{
		++ cnt;
        v[cnt] = s * a;
		w[cnt] = s * b;
	}
}

// 01 背包
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;

(4)分组背包

for (int i = 1; i <= n; i ++)
    for (int j = m; j >= 0; j --)
        for (int k = 1; 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;

2. 线性DP

最长上升子序列(LIS)

for (int i = 1; i <= n; i ++)
{
	f[i] = 1;
	for (int j = 1; j <= i; j ++)
		if (a[j] < a[i])				// 注意是否为 严格递增 或 单调不减
			f[i] = max(f[i], f[j] + 1);
}

cout << *max_element(f + 1, f + 1 + n) << endl;

3. 区间DP

// dp[l, r] 拆分为 dp[l, k - 1] 与 dp[k - 1, r]

4. 树形DP

// 配合dfs

六、常用库

// 向上取整
(a + b - 1) / b

// 四舍五入
round()

// 进制转换
string s;
cin >> s;
char *c;
cout << strtol(s.c_str(), &c, 10) << endl;

// pi
acos(-1.0)
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐