蓝桥杯算法

第一章 递推与递归

1.1 递归

所有递归都对应一棵递归搜索树

image-20250206184548388

AcWing 820. 递归求斐波那契数列

#include<iostream>

using namespace std;


int Fib(int n)
{
    if (n == 1 or n == 2) return 1;
    else return Fib(n - 1) + Fib(n - 2);
}

int main()
{
    int n;
        cin >> n;
        cout << Fib(n) << '\n';
    return 0;
}

AcWing 92. 递归实现指数型枚举

递归(DFS)

image-20250207161421103

参考代码

#include<iostream>
#include<vector>

using namespace std;
const int N = 16;

int n;
int st[N];	// s[i]记录当前位置数字的状态,0表示未考虑,1表示选择,2表示不选


void dfs(int u)
{
	if (u > n) {
		for (int i = 1; i <= n; i++) {
			if (st[i] == 1) {
				cout << i << ' ';
			}
		}
		cout << '\n';
		return;
	}

	st[u] = 2;	// 第一个分支:不选
	dfs(u + 1);

	st[u] = 1;	// 第二个分支:选
	dfs(u + 1);
}


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

	return 0;
}

AcWing 93. 递归实现组合型枚举

image-20250207175727342

参考代码

// coding by xiongdx 
#include<iostream>

using namespace std;

const int N = 15;

int n, m;
int way[N];		// n个位置

void dfs(int u, int start)  // 当前递归到第u个位置,至少从start开始选
{
    if (n - start + 1 < m - u + 1) return;  // 剪枝:如果剩余的数字不够填满空位置,退出
	if (u > m) {	// 递归终点,输出答案
		for (int i = 1; i <= m; i++) {
			cout << way[i] << ' ';
		}
		cout << '\n';
		return;
	}

	for (int i = start; i <= n; i++) {	// 位置u依次放入 start~n 的每个数字
		way[u] = i;			// 第u个位置填入数字i
		dfs(u + 1, i + 1);	// 递归

		//way[u] = 0;			// 恢复现场
	}
}

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

	dfs(1, 1);		// 递归起点

	return 0;
}

AcWing 94. 递归实现排列型枚举

image-20250207163939794

参考代码

#include<iostream>

using namespace std;

const int N = 10;

int n;
int state[N];	// 表示当前状态,0表示未放数,i表示放入数字i
bool used[N];	// 表示该数是否被用过

void dfs(int u)
{
	if (u > n) {	// 边界
		for (int i = 1; i <= n; i++) cout << state[i] << ' ';	// 打印方案
		cout << '\n';
		return;
	}

	// 依次枚举每个分支,即当前位置可以填哪些数
	for (int i = 1; i <= n; i++) {	// 枚举每一个数
		if (!used[i]) {	// 如果数字i未被使用过
			state[u] = i;	// 填入当前数字i
			used[i] = true;	// 表示数字i已经被用过了
			dfs(u + 1);		// 递归

			// 恢复现场
			//state[u] = 0;
			used[i] = false;
		}
	}
}

int main()
{
	cin >> n;

	dfs(1);

	return 0;
}

蓝桥杯真题:带分数

n = a + b / c,可推得 b = n * c - a * c

#include<iostream>
#include<cstring>

using namespace std;

const int N = 20;

int n;
int ans;        // 统计满足条件的组合数
bool st[N], backup[N];	// st[N]:标记数字 1-9 是否已被使用。
						// backup[N]:临时备份 st 的状态,用于检查 b 的合法性。

bool check(int a, int c)	// 验证 a、b、c 是否满足条件
{
	long long b = n * (long long)c - a * (long long)c;	// n = a + b / c

	if (!a || !b || !c) return false;	// a,b,c 都不能是0

	memcpy(backup, st, sizeof st);
	// 检查 b 的每一位是否合法(无重复且不含 0)
	while (b) {
		int x = b % 10;		// 每次取出末位
		b /= 10;			// 删除末位
		if (!x || backup[x]) return false;	// x不是0且x没有被用过
		backup[x] = true;	// 标记x被用过
	}

	for (int i = 1; i <= 9; i++) {
		if (!backup[i]) {
			return false;	// 如果1~9存在某个数字没有出现
		}
	}

	return true;
}


void dfs_c(int u, int a, int c)
{
	if (u == 9) return;		// 可行性剪枝:如果用完了9种数字,返回

	if (check(a, c)) ans++;	// 检查 a,b,c 是否满足要求

	for (int i = 1; i <= 9; i++) {  // 枚举c的数字组合
		if (!st[i]) {						// 如果没用过
			st[i] = true;					// 标记用过
			dfs_c(u + 1, a, c * 10 + i);	// 继续递归生成更长的c
			st[i] = false;                  // 恢复现场
		}
	}
}


void dfs_a(int u, int a)	// u表示已经用了多少数字
{
	if (a >= n) return;		// 可行性剪枝

	if (a) dfs_c(u, a, 0);   // 保证a不是0

	for (int i = 1; i <= 9; i++) {	// 枚举1~9数字
		if (!st[i]) {       // 如果数字i没有被用过
			st[i] = true;	// 标记用过
			dfs_a(u + 1, a * 10 + i);   // 继续递归
			st[i] = false;	// 恢复现场
		}
	}
}


int main()
{
	cin >> n;

	dfs_a(0, 0);	// 递归起点

	cout << ans << endl;	// 输出答案

	return 0;
}

1.2 递推

AcWing 717. 简单斐波那契

具体参考这里

参考代码

// Encoded by xiongdx
#include<iostream>

using namespace std;

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

    int f1 = 0, f2 = 1;
    cout << f1 << ' ';  // N > 0,所以肯定要输出第一项
    n--;                // 否则会多输出一项
    while(n--) {
        cout << f2 << ' ';  // 每次输出新计算的一项
        int t = f2;
        f2 = f1 + f2;       // 递推公式
        f1 = t;             // 滚动变量
    }
    return 0;
}

AcWing 95. 费解的开关

递归:将问题分解为相同的子问题,直到最简单的形式可以直接得出答案,再返回子问题的结果求得原问题答案

递推:先求出子问题,再用子问题求出原问题 (相当于递归的后半部分)

有题可推得

  1. 开关的按动顺序不影响结果

  2. 每个格子只能按动1次或0次(因为按偶数次等于按0次,奇数次等于1次)

  3. 从上往下逐行按动开关,那么第i行开关是否按动被第i-1行的灯泡状态唯一确定

    即如果第(i-1, j)个灯泡是灭的,那么到第(i, j)个灯泡必须按动,否则第(i-1, j)个灯泡无法被点

因此,只需枚举第一行所以灯泡是否按动开关,即可递推出是否能在6步内点亮所以灯泡。

参考代码

#include<cstdio>
#include<cstring>

using namespace std;
const int N = 6;
char g[N][N], bg[N][N];
int dx[5] = {-1, 0, 1, 0, 0}, dy[5] = {0, 1, 0, -1, 0};
// 1. 每个开关最多会被按动一次(因为按动多次效果一样)
// 2. 最终的结果与开关按动的顺序无关(因为每个灯最终的状态只于被按几次有关系)
// 3. 从上往下逐行按动开关,那么第i行开关是否按动被第i-1行的灯泡状态唯一确定

void turn(int x, int y) //按动第x行第y列的开关
{
    for (int i = 0; i < 5; i++){
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= 5 || b < 0 || b >= 5) continue;
        g[a][b] ^= 1; 
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--){
        for (int i = 0; i < 5; i++) scanf("%s", bg[i]);
        
        int res = 10;
        for (int op = 0; op < 32; op++){    // 利用二进制枚举第一行的所有可能
            int cnt = 0;
            memcpy(g, bg, sizeof g); // 把二维字符数组bg的元素拷贝到g数组中进行操作
            for (int i = 0; i < 5; i++){
                if (op >> i & 1) {  // 二进制op为1的位表示按动开关
                    turn(0, i);
                    cnt++;
                }
            }
            
            // 第一行枚举固定,不断由上一行递推下一行的状态(即是0需要按,是1不需要按)
            for (int i = 0; i < 4; i++){
                for (int j = 0; j < 5; j++){
                    if (g[i][j] == '0'){
                        turn(i + 1, j); // g[i][j]为0,则需要按动下一行的开关
                        cnt++;
                    }
                }
            }
            
            // 检查最后一行灯的状态(是否全亮)
            bool success = true;
            for (int i = 0; i < 5; i++){
                if (g[4][i] == '0'){
                    success = false;
                }
            }
            
            if (success && res > cnt) res = cnt;
        }
        
        if (res > 6) res = -1;
        printf("%d\n", res);
    }
    
    return 0;
}

蓝桥杯真题:翻硬币

参考代码

#include<iostream>

using namespace std;

string s1, s2;
int n, ans;

int main()
{
    cin >> s1 >> s2;
    
    n = s1.size();
    
    for (int i = 0; i < n; i++) {
        if (s1[i] != s2[i]) {   // 如果发现不相等,必定要翻动且只翻动一次
            ans++;              // 更新次数
            if (i != n - 1) {   // 实现翻硬币
                if (s1[i + 1] == '*') s1[i + 1] = 'o';
                else s1[i + 1] = '*';
            }
        }
    }
    
    cout << ans << endl;    // 输出答案,数据保证一定有解
    
    return 0;
} 

AcWing 116. 飞行员兄弟

  1. 枚举所有方案 0 ~ 2^16 - 1
  2. 按照每种方案对所有灯泡进行操作
  3. 判断并记录成功方案

维护最小步数及其方案

保证输出步数最小的方案中字典序最小的方案:当两个方案步数相同(即二进制中的1的个数相同)时,优先枚举1更靠前(即从小到大)的方案。

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

using namespace std;

typedef pair<int, int> PII;

const int N = 5;

char g[N][N], backup[N][N];


int get(int x, int y)	// 求坐标(x, y)对应的 0~15 中的数字
{
	return x * 4 + y;
}

void turn_one(int x, int y)
{
	if (g[x][y] == '+') g[x][y] = '-';
	else g[x][y] = '+';
}

void turn_all(int x, int y)
{
	for (int i = 0; i < 4; i++) {
		turn_one(x, i);		// 按动一次第x行的所有开关
		turn_one(i, y);		// 按动一次第y列的所有开关
	}
	turn_one(x, y);			// 多了一遍(x, y)的开关
}

int main()
{
	for (int i = 0; i < 4; i++) cin >> g[i];

	vector<PII> res;
	for (int op = 0; op < 2 << 16; op++) {	// 1. 枚举所有方案
		vector<PII> temp;
		memcpy(backup, g, sizeof g);		// 备份当前状态
		for (int i = 0; i < 4; i++) {		// 行	
			for (int j = 0; j < 4; j++) {	// 列
				if (op >> get(i, j) & 1) {	// 如果当前位为1,进行操作
					temp.push_back({ i, j });
					turn_all(i, j);
				}
			}
		}

		// 判断所有灯泡是否全亮
		bool has_closed = false;
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				if (g[i][j] == '+') {
					has_closed = true;
				}
			}
		}

		if (has_closed == false) {
			if (res.empty() || res.size() > temp.size()) res = temp;
		}

		memcpy(g, backup, sizeof backup);	// 还原备份
	}

	cout << res.size() << endl;
	for (auto op : res) cout << op.first + 1 << ' ' << op.second + 1 << endl;
}

第二章 二分和前缀和

2.1 二分查找

image-20250209181134329

整数二分步骤:

  1. 找到一个区间[L, R],使得答案一定在该区间中
  2. 找一个判断条件,使得该判断条件具有二段性,并且答案一定在二段性的分界点
  3. 分析中点M在该条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间
  4. **如果数组左半部分满足check(),则 [l, r] 分为 [l, M-1] 和 [M,r],且其中M = (l + r + 1) / 2 **
  5. **否则如果数组右半部分满足check(),则 [l, r] 分为 [l, M] 和 [M+1,r],且其中M = (l + r) / 2 **

2.1.1 整数二分

AcWing 789. 数的范围

参考代码

#include<iostream>

using namespace std;

const int N = 100010;

int n, q;
int a[N];

void solve()
{
    int k;
    cin >> k;
    int l = 0, r = n - 1;
    while (l < r) {  // 二分x的左端点
        int mid = (l + r) / 2;
        if (a[mid] >= k) r = mid;
        else l = mid + 1;
    }

    if (a[l] == k) {
        cout << l << ' ';   // 输出左端点

        r = n - 1;
        while (l < r) {  // 二分x的右端点
            int mid = (l + r + 1) / 2;
            if (a[mid] <= k) l = mid;
            else r = mid - 1;
        }
        cout << r << endl;  // 输出右端点
    }
    else cout << -1 << ' ' << -1 << '\n';
}

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

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

    while (q--) {
        solve();
    }
}
蓝桥杯真题:机器人跳跃问题

关键:防止爆long long

参考代码

#include<iostream>

using namespace std;

typedef long long ll;

const int N = 100010;

ll n;
ll maxh = 0;    // 记录最大的h
ll h[N];

bool check(ll E)
{
	for (int i = 0; i < n; i++) {
		E += E - h[i + 1];
		if (E > maxh) return true;  // 一定满足,返回以防止爆long long
		if (E < 0) return false;
	}
	return true;
}

int main()
{
	cin >> n;
    
	for (int i = 1; i <= n; i++) {
	    cin >> h[i];
	    maxh = max(maxh, h[i]);
	}

	// 数据范围n <= 10^5, 二分检查是否满足条件时间复杂度为O(nlogn)
	ll l = 0, r = 100000, mid = 0;

	while (l < r) {
		mid = (l + r) >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}

	cout << l << endl;
	
	return 0;
}
蓝桥杯真题:四平方和

解法一:暴力(蓝桥杯官网能过)

直接暴力枚举a, b, c的可能值,最后计算出$d = sqrt(n - a^2 - b^2 - c^2)$

时间复杂度:$O(n^{2/3})$

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

using namespace std;

const int N = 500010;

int n;

int main()
{
	cin >> n;
	
    // 暴力枚举a, b, c所有可能的值,并求出d
	for (int a = 0; a * a <= n; a++) {
		for (int b = a; a * a + b * b <= n; b++) {
			for (int c = b; a * a + b * b + c * c <= n; c++) {
				int t = n - a * a - b * b - c * c;
				int d = sqrt(t);
				if (d * d == t) {
					printf("%d %d %d %d\n", a, b, c, d);
					return 0;
				}
			}
		}
	}

	return 0;
}

解法二:暴力枚举+二分

空间换时间,先用O(n^2)时间复杂度把c^2 + d^2的所有可能值暴力枚举出并记录下来以及cd的值,然后用O(n^2longn)的时间复杂度枚举a,b的可能值并二分求出对应的c^2 + d^2是否存在,并求出cd

时间复杂度:$O(nlogn)$

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

using namespace std;

const int N = 2500010;

struct Sum
{
	int s, c, d;
	bool operator < (const Sum& t) const	// 重载小于号,保证字典序最小
	{
		if (s != t.s) return s < t.s;
		if (c != t.c) return c < t.c;
		return d < t.d;
	}
}sum[N];

int n, m;

int main()
{
	cin >> n;

	for (int c = 0; c * c <= n; c++) {
		for (int d = c; c * c + d * d <= n; d++) {
			sum[m++] = { c * c + d * d, c, d };		// 先将c^2 + d^2计算出来
		}
	}
	
	sort(sum, sum + m);		// 排序, 保证单调性以二分

	for (int a = 0; a * a <= n; a++) {
		for (int b = a; a * a + b * b <= n; b++) {
			int t = n - a * a - b * b;  // 需要的c^2+d^2
			int l = 0, r = m - 1;
			while (l < r) {             // 二分查找是否存在
				int mid = l + r >> 1;
				if (sum[mid].s >= t) r = mid;
				else l = mid + 1;
			}
			if (sum[l].s == t) {        // 如果存在,输出答案 
				printf("%d %d %d %d\n", a, b, sum[l].c, sum[l].d);
				return 0;
			}
		}
	}

	return 0;
}

解法三:数组模拟哈希

与解法二类似,不同的是这里用哈希数组来存c^2 + d^2的值,维护哈希表hx[c^2+d^2] = c,通过c计算得出d

时间复杂度:$O(n)$

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

using namespace std;

const int N = 5000010;

int n;
int cd[N];	// 哈希数组存放c^2 + d^2以及对应的c

int main()
{
	cin >> n;
    
    memset(cd, -1, sizeof cd);
	for (int c = 0; c * c <= n; c++) {	// 将c^2+d^2所有可能的值打表用哈希数组记录
		for (int d = c; c * c + d * d <= n; d++) {
			int t = c * c + d * d;
			if (cd[t] == -1) cd[t] = c;	// 第一个即是字典序最小的
		}
	}

	for (int a = 0; a * a <= n; a++) {	// 暴力枚举a和b可能得值
		for (int b = a; a * a + b * b <= n; b++) {
			int t = n - a * a - b * b;	// 求出 c^2 + d^2
			if (cd[t] != -1) {			// 如果存在
			    int c = cd[t];			
			    int d = sqrt(t - c * c);// 根据c和c^2+d^2求出d
				printf("%d %d %d %d\n", a, b, c, d);	// 输出答案
				return 0;
			}
		}
	}

	return 0;
}
蓝桥杯真题:分巧克力

image-20250211181852271

参考代码

#include<iostream>

using namespace std;

typedef pair<int, int> PII;
typedef long long ll;

const int N = 100010;

int n, k;
int h[N], w[N];

bool check(int m)
{
	ll cnt = 0;     // 暴力枚举判断是否满足条件
	for (int i = 0; i < n; i++) {
		cnt += (h[i] / m) * (w[i] / m);     // 注意括号!!!否则就不是下取整
	}
	
	return cnt >= k;
}

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

	int l = 0, r = 0;
	for (int i = 0; i < n; i++) {
		cin >> h[i] >> w[i];
		r = max(r, h[i]);
		r = max(r, w[i]);   // r取边长的最大值
	}
	
	while (l < r) {     // 二分答案
		int mid = (l + r + 1) >> 1;
		if (check(mid)) l = mid;
		else r = mid - 1;
	}

	cout << l << endl;
	return 0;
}

1.3.2 实数二分:数的三次方根

#include<iostream>

using namespace std;

int main()
{
	double x;
	scanf("%lf", &x);

	double l = -10000, r = 10000, mid = 0;	// 避免小数时出错,比如0.001的答案是0.1,答案不在[0, 0.001]的范围内

	while (r - l >= 1e-8) {
		mid = (l + r) / 2;
		if (mid * mid * mid >= x) r = mid;
		else l = mid;
	}

	printf("%.6f", mid);	// 保留6位小数

	return 0;
}

2.2 前缀和

2.2.1 一维前缀和

算法思路:

S[i] = a[1] + a[2] + ... a[i]

Si = Si-1 + ai

建议下标从从1开始,令S0 = 0,这样求[1,10]就是S10 - S0 = S10

作用:快速求出任意区间的和

例如:求[l,r]范围内ai的和,可以用 Sr - Sl-1

a[l] + ... + a[r] = S[r] - S[l - 1]

如果询问下标从0开始:

a[l] + ... + a[r] = S[r + 1] - S[l ]

示例代码:

#include<iostream>

using namespace std;

const int N = 100010;

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

int main()
{
	scanf_s("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) scanf_s("%d", &a[i]);
	for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];	//前缀和的初始化
	s[0] = 0;

	while (m--)
	{
		int l, r;
		scanf_s("%d%d", &l, &r);
		printf("%d\n", s[r] - s[l - 1]);	//区间和的计算
	}
	return 0;
}
蓝桥杯真题:k倍区间

前缀和

image-20250212211004216

空间换时间:提前预处理出[L, R]中余数相同的个数

image-20250213102758588

算法思路

  • 前缀和性质:若两个前缀和s[i]s[j]的余数相同,则子数组i+1j的和为k的倍数。
  • 余数统计:通过cnt数组记录各余数的出现次数,每遇到相同余数时,这些位置均可形成有效子数组。
  • 初始化cnt[0]:确保直接以当前元素结尾且和为k倍数的子数组被正确计数。

参考代码

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

using namespace std;

typedef long long LL;

const int N = 100010;

int n, k;
LL s[N], cnt[N];	// s[i] 表示前i个元素的前缀和,cnt[i] 统计余数为i的前缀和出现的次数

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

	for (int i = 1; i <= n; i++) {
		scanf("%lld", &s[i]);
		s[i] += s[i - 1];       // 计算前缀和
	}

	LL res = 0;                 // 存储答案
	cnt[0] = 1;                 // 初始化前缀和是k的倍数的情况
	for (int r = 1; r <= n; r++) {
		res += cnt[s[r] % k];   // res加上与s[r]%k余数相同的前缀和的数量
		cnt[s[r] % k]++;        // s[r]%k 的余数加一
	}

	printf("%lld\n", res);      // 输出答案
	return 0;
}

2.2.2 二维前缀和

二维前缀和的计算公式:

$S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + a[i][j]$

计算区间(x1, y1)(x2, y2)的和:

$S[x2][y2] - S[x1 - 1][y2] - S[x2][y1 - 1] + S[x1 - 1][y1 - 1]$

image-20250211202400169

AcWing 796.子矩阵的和
#include<iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int s[N][N];

int main()
{
    cin >> n >> m >> q;
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> s[i][j];
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
        }
    }
    
    while(q--) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        cout << s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << endl;
    }
    
    return 0;
}
AcWing 99. 激光炸弹

参考代码:

#include<bits/stdc++.h> 

using namespace std;
using ll = long long;
using PII = pair<int, int>;
const int N = 5e3 + 10;

int n, m;
int s[N][N];

void solve()
{
    int r;
    cin >> n >> r;
    r = min(r, 5001); // r 达到5001即可全部清除

    for (int i = 0; i < n; i++) {
        int x, y, w;
        cin >> x >> y >> w;
        x++, y++;
        s[x][y] += w;
    }

    // 二维前缀和优化时间
    for (int i = 1; i <= 5001; i++) {
        for (int j = 1; j <= 5001; j++) {
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; // 这里只使用一个数组优化空间
        }
    }

    // 遍历所有(R-1)×(R-1)的矩阵求出最大值,利用二维前缀和优化时间复杂度
    int sums = 0;
    for (int i = r; i <= 5001; i++) {   // 枚举矩阵的右下角(i, j)
        for (int j = r; j <= 5001; j++) {
            sums = max(sums, s[i][j] - s[i - r][j] - s[i][j - r] + s[i - r][j - r]);
        }
    }

    cout << sums << endl;

}


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

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

// Created by xiongdx on 9/9/2024

第三章 数学与简单DP

3.1 数学知识

AcWing1205. 买不到的数目

打表找规律

n m ans
3 2 1
3 4 5
3 5 7
3 7 11
3 8 13
3 10 17

n m ans
5 2 3
5 3 7
5 4 11
5 6 19
5 7 23
5 8 27
5 9 31

得出规律:

n==3时,ans = 2*m - 3

n==5时,ans = 4*m - 5

即当n确定时,ans = (n - 1) * m - n

又显然nm等价,故

$ans = nm - n - m = (n - 1)(m - 1) - 1$

代入验证可知正确。

结论

如果$a, b$是正整数且互质,那么由$ax+by, x >= 0, y >= 0$不能凑出的最大整数是$ab - a - b$

具体证明可参考这里

参考代码

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

using namespace std;

typedef long long LL;

LL n, m;

int main()
{
    cin >> n >> m;
    
    LL ans = n * m - n - m;
    
    cout << ans << endl;
    return 0;
}

暴力枚举(TLE)

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

using namespace std;

const int N = 1010;

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

bool dfs(int x)
{
	if (x == 0) return true;	// 如果x被减到0,说明可以凑出来

	if (x >= n && dfs(x - n)) return true;	// 用n凑
	if (x >= m && dfs(x - m)) return true;	// 用m凑

	return false;	// 都凑不出来
}

int main()
{
	cin >> n >> m;
	int res = 0;
	for (int i = 1; i <= 1000; i++) {
		if (!dfs(i)) res = i;
	}

	cout << res << endl;
	return 0;
}
AcWing 1211. 蚂蚁感冒

关键:蚂蚁相遇等价于蚂蚁互相穿过对方

算法思路:

先统计被感染的蚂蚁左边且向右走的蚂蚁数量lr,再统计被感染的蚂蚁右边且向左走的蚂蚁数量rl

  • 如果被感染的蚂蚁是向左走的,并且没有在它左边且向右走的蚂蚁,或者被感染的蚂蚁是向右走的,并且在它右边且向左走的蚂蚁

    那么,只有初始的一个蚂蚁感冒

  • 否则如果被感染的蚂蚁前往的方向上存在“逆行”的蚂蚁

    那么所有“逆向”的蚂蚁都会被感染,加上初始的一个蚂蚁,答案是lr + rl + 1

参考代码

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

using namespace std;

const int N = 55;

int n;
int a[N];

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

	int lr = 0, rl = 0;	// 分别表示左边向右走的蚂蚁数量和右边向左走的蚂蚁数量
	for (int i = 1; i < n; i++) {
		if (abs(a[i]) < abs(a[0]) && a[i] > 0) lr++;	
		else if (abs(a[i]) > abs(a[0]) && a[i] < 0) rl++;
	}

	if (a[0] > 0 && rl == 0 || a[0] < 0 && lr == 0) cout << 1 << endl;	// 只有最初的一个蚂蚁感冒
	else cout << lr + rl + 1 << endl;	// 会把所有反着走的都感染

	return 0;
}

3.2 动态规划

蓝桥杯真题:地宫取宝

递归解法

2.1 思路
当走到某个格子上的时候:

  1. 如果格子上宝贝的价值大于已有宝贝的最大值,那么可以选择拿或者不拿。

  2. 如果格子上宝贝的价值小于或者等于已有宝贝的最大值,那么只能选择不拿。

  3. 必须从左上角走到右下角,且只要到达右下角时物品个数满足条件即算一种方案。

  4. 只能选择向下或者向右走

  5. 不是必须到出口时,宝贝数量恰好满足条件,而是可以在任意位置就宝贝数量就可以满足条件,只需保证到达出口时宝贝数量仍然满足条件即可

参考代码

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

using namespace std;
typedef long long LL;
const int N = 55, MOD = 1e9 + 7;

int n, m, k;
int w[N][N];			// 存储宝物价值
int dp[N][N][13][14];	// f[i][j][k][w] 从起点走到(i, j)且已经取了k件物品且最后一件物品的价值是w的合法方案的集合(个数)
int dx[2] = { 1, 0 };
int dy[2] = { 0, 1 };

// 分别表示当前搜到的坐标(x, y),当前已经拿的宝物数量cnt,当前宝物的最大值mx
LL dfs(int x, int y, int cnt, int mx)	
{
	if (x == n && y == m) return cnt == k ? 1 : 0;	// 搜到终点时返回,宝物个数等于k时返回1,否则为0
	if (dp[x][y][cnt][mx] != -1) return dp[x][y][cnt][mx];  // 计算过久直接返回结果
	
	LL res = 0;		// 记录最大方案数
	for (int i = 0; i < 2; i++) {	// 枚举两个方向
		int nx = x + dx[i], ny = y + dy[i];
		if (nx < 1 || nx > n || ny < 1 || ny > m) continue;	// 越界
		if (w[nx][ny] > mx && cnt < k) res = (res + dfs(nx, ny, cnt + 1, w[nx][ny])) % MOD;	// 拿
		res = (res + dfs(nx, ny, cnt, mx)) % MOD;
	}
	dp[x][y][cnt][mx] = res;    // 记忆化搜索,存储结果避免重复计算
	return res;
}

int main()
{
	memset(dp, -1, sizeof dp);	// 初始化dp为-1,判断是否已经被更新

	cin >> n >> m >> k;

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> w[i][j];
			w[i][j]++;	// 让所有价值都加一,避免第一个物品可能是0而导致无法取到
		}
	}

	LL ans = (dfs(1, 1, 0, 0) + dfs(1, 1, 1, w[1][1])) % MOD;	// 对第一个宝物拿或不拿两种情况分别dfs

	cout << ans << endl;    // 输出答案
	return 0;
}

动态规划

摘花生+最长上升子序列 求方案数 四维dp

image-20250215164641961

参考代码

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

using namespace std;

const int N = 55, MOD = 1e9 + 7;

int n, m, k;
int w[N][N];
int f[N][N][13][14];    // f[i][j][k][w] 从起点走到(i, j)且已经取了k件物品且最后一件物品的价值是w的合法方案的集合(个数)

int main() 
{
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> w[i][j];
            w[i][j]++;  // 所有值加一防止数组越界
        }
    }

    f[1][1][1][w[1][1]] = 1;    // 选第一件物品
    f[1][1][0][0] = 1;          // 不选第一件物品

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {  // 枚举坐标(i, j)
            if (i == 1 && j == 1) continue;
            for (int u = 0; u <= k; u++) {  // 枚举已选物品的个数
                for (int v = 0; v <= 13; v++) {     // 枚举最后一个物品的价值
                    // 不选
                    f[i][j][u][v] = (f[i][j][u][v] + f[i - 1][j][u][v]) % MOD;
                    f[i][j][u][v] = (f[i][j][u][v] + f[i][j - 1][u][v]) % MOD;
                    if (u > 0 && v == w[i][j]) { // 选
                        for (int c = 0; c < v; c++) {
                            f[i][j][u][v] = (f[i][j][u][v] + f[i - 1][j][u - 1][c]) % MOD;
                            f[i][j][u][v] = (f[i][j][u][v] + f[i][j - 1][u - 1][c]) % MOD;
                        }
                    }
                }
            }
        }
    }
    
    int res = 0;    // 统计最大的方案数
    for (int i = 0; i <= 13; i++) res = (res + f[n][m][k][i]) % MOD;

    cout << res << endl;
    return 0;
}
蓝桥杯真题:波动数列
  • 所有满足条件的$(n-1)d_1 + (n-2)d_2 + ... + d_{n-1}$ 与 $s$ 模 $n$ 的余数相同

image-20250216164443355

闫氏DP分析法(类似背包问题??)

image-20250216170208916

参考代码

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

using namespace std;

const int N = 1010, MOD = 1e8 + 7;

int f[N][N];	// 前i项中,当前在总和模n的余数是j的方案的集合(总数)

// 计算 a 除以 b 的正余数
int get_mod(int a, int b)
{
	return (a % b + b) % b;
}

int main()
{
	int n, s, a, b;
	cin >> n >> s >> a >> b;

	f[0][0] = 1;
	for (int i = 1; i < n; i++) {	// 前 n - 1 项
		for (int j = 0; j < n; j++) {	// 模n的余数 0 ~ n - 1
			f[i][j] = (f[i - 1][get_mod(j - a * (n - i), n)] + f[i - 1][get_mod(j + b * (n - i), n)]) % MOD;
		}
	}

	cout << f[n - 1][get_mod(s, n)] << endl;    // 避免s为负时模n数组越界
	return 0;
}

第四章 枚举、模拟与排序

image-20250216173908925

4.1 枚举

蓝桥杯真题:连号区间数

连号区间的极差等于区间长度-1

参考代码

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

using namespace std;

const int N = 10000;

int n;
int a[N];

int main()
{
	cin >> n;

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

	int cnt = 0;	// 连号区间数量
	for (int i = 1; i <= n; i++) {
		int mx = a[i], mn = a[i];	// 最大值和最小值
		for (int j = i; j <= n; j++) {
			mx = max(mx, a[j]);
			mn = min(mn, a[j]);
			
			// 如果区间的极差等于区间长度-1,则为连号区间
			if ((mx - mn) == (j - i)) cnt++;
		}
	}

	cout << cnt << endl;
	return 0;
}

蓝桥杯真题:递增三元组

解法一:二分查找

数据范围:$1 ≤ N ≤ 10^5$,故时间复杂度应该在$O(n longn)$,即只能枚举一个数组,因为数组B与数组A和数组C都存在直接关系,故应枚举数组B,然后二分判断数组A中有多少元素小于B[j](数组A中第一个小于B[j]的元素下标为i),再二分判断数组C中有多少元素大于B[j](数组C中第一个小于B[j]的元素下标为k),最后cnt += (LL)(i + 1) * (LL) (n - k)

参考代码

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

using namespace std;
typedef long long LL;

const int N = 100010;

int n;
int a[N], b[N], c[N];

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

	sort(a, a + n);
	//sort(b, b + n);	// 数组B不用排序
	sort(c, c + n);

	LL cnt = 0;
	for (int j = 0; j < n; j++) {   // 枚举数组b,a和c都与b有联系
		int l = 0, r = n - 1;
		while (l < r) {				// 二分数组A
			int mid = l + r + 1 >> 1;
			if (a[mid] < b[j]) l = mid;
			else r = mid - 1;
		}
		int i = l;					// 第一个小于B[j]的下标

		l = 0, r = n - 1;
		while (l < r) {				// 二分数组C
			int mid = l + r >> 1;
			if (c[mid] > b[j]) r = mid;
			else l = mid + 1;
		}
		int k = l;					// 第一个大于B[j]的下标
		
        // 判断结果是否符合(可能存在C数组中所以元素都小于B[j]的情况)
		if (a[i] < b[j] && b[j] < c[k]) cnt += (LL)(i + 1) * (LL)(n - k);
	}

	printf("%lld\n", cnt);
	return 0;
}

解法二:前缀和

同上,枚举数组$B$的每个元素,对于每个元素$B_j$,求

  • 在数组$A$中有多少个元素小于$B_j$

  • 在数组$C$中有多少个元素大于$B_j$

首先维护一个数组$cnt[i]$,表示在数组$A$中元素$i$出现的次数cnt[A[i]]++,然后求数组$cnt[i]$的前缀和$s[i]$,表示在数组$A$中元素$0-i$出现的次数。则:

  • 在数组$A$中小于$B_j$ 的元素个数 = $s[B_j - 1]$
  • 在数组$C$中大于$B_j$ 的元素个数 = $s[N] - s[B_j]$

则总个数cnt += s[B[j] - 1] * s[N] - s[B[j]];

image-20250217123841187

参考代码

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

using namespace std;
typedef long long LL;

const int N = 100010;

int n;
int cntA[N], cntC[N];	// 分别表示在数组A和数组C中元素i出现的次数
int B[N];				// 存储数组B
LL sA[N], sC[N];		// 分别表示在数组A和数组C中元素0-i出现的次数

int main()
{
	scanf("%d", &n);
	
	// 读入数据并统计次数
	for (int i = 1; i <= n; i++) {
		int t;
		scanf("%d", &t);
		cntA[++t]++;    // 因为数据从0开始,前缀和需要从1开始,故所有数据加一
	}
	for (int j = 1; j <= n; j++) scanf("%lld", &B[j]), B[j]++;
	for (int k = 1; k <= n; k++) {
		int t;
		scanf("%d", &t);
		cntC[++t]++;
	}

	// 计算前缀和
	for (int i = 1; i <= N; i++) sA[i] = sA[i - 1] + cntA[i];
	for (int k = 1; k <= N; k++) sC[k] = sC[k - 1] + cntC[k];
	
	// 计算递增三元组
	LL cnt = 0;		// 存储答案
	for (int j = 1; j <= n; j++) {	// 枚举数组B
		LL a = sA[B[j] - 1], c = sC[N] - sC[B[j]];	// 计算个数
		cnt += a * c;
	}
	
	printf("%lld\n", cnt);	// 输出答案
	return 0;
}

蓝桥杯真题:特别数的和

暴力枚举每一个数,把每个数的数位取出来单独判断即可

常用小技巧:

  1. 取出x的每位数字
int x = 2019;
string str;
while(x) {
    int t = x % 10;
	x /= 10;
}
  1. 将字符数字转为数字
string str = "2019";
int x = 0;
for (int i = 0; i < str.size(); i ++ )
    x = x * 10 + str[i] - '0';

参考代码

#include<iostream>

using namespace std;

int n;

bool check(int x)
{
    while(x){
        int t = x % 10;
        if (t == 2 || t == 0 || t == 1 || t == 9) return true;
        x /= 10;
    }
    
    return false;
}

int main()
{
    cin >> n;
    
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if (check(i)) ans += i;
    }
    
    cout << ans << endl;
    return 0;
}

蓝桥杯真题:错误票据

读入一行数据使用getline(cin, str)

注意:getline(cin, str)会把结尾的换行也读入!具体见下面代码第17行

时间复杂度:$O(n)$

参考代码

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n;
int a[N];
int cnt[N];

int main()
{
	int t = 0;
	cin >> t;
	string line;
    
	getline(cin, line);	// 忽略第一行的回车
	while (t--) {
		getline(cin, line);			// 从标准输入读取一行字符串,存储到 line 中
		stringstream ssin(line);	// 将 line 包装成一个字符串流 ssin,方便逐个提取其中的整数
        
        // 从字符串流 ssin 中逐个提取整数,存储到数组 a 中,并更新 n(数组 a 的当前元素个数)
		while (ssin >> a[n]) {
		    cnt[a[n]]++;    // 统计每个数字出现的次数
		    n++;
		}
	}

	int less = 0, more = 0, flag = 0;       // 分别表示缺号、重号、是否都已找到  
	int start = 1;      // 记录起点
	while (cnt[start] == 0) start++;
	for (int i = start; i <= 100000; i++) {
		if (cnt[i] == 0) less = i, flag++;  // 缺号
		if (cnt[i] == 2) more = i, flag++;  // 重号
		if (flag == 2) break;               // 如果两个都找到,返回
	}
	
	cout << less << ' ' << more << endl;    // 输出答案
	return 0;
}
// Encoding by xiongdx.

蓝桥杯真题:回文日期1

日期类问题+字符串处理

参考代码

#include <iostream>
#include <bits/stdc++.h>

using namespace std;

void f() { exit(0); }

int n;
int dn[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

int check(string s)
{
	int sn = s.size();

	// 判断是否是回文串
	for (int i = 0, j = sn - 1; i <= j; i++, j--) {
		if (s[i] != s[j]) return 0;
	}

	// 判断是否是ABABBABA型
	if (s[0] != s[1] && s[0] == s[2] && s[1] == s[3]) return 2;

	return 1;
}


int main()
{
	cin >> n;

	int dd = n % 100;
	n /= 100;
	int mm = n % 100;
	int yy = n / 100;
	bool f1 = false, f2 = false;

	// 先把题目给的从 dd+1 天开始的月循环完
	for (int d = dd + 1; d <= dn[mm]; d++) {
		string s = "";
		s += to_string(yy);
		if (mm < 10) s += '0';
		s += to_string(mm);
		if (d < 10) s += '0';
		s += to_string(d);
		int res = check(s);
		if (!f1 && res >= 1) {
			f1 = true;
			cout << s << endl;
		}

		if (!f2 && res == 2) {
			f2 = true;
			cout << s << endl;
		}
		if (f1 && f2) return 0;
	}

	// 再把题目中给的从 mm+1 月开始的年循环完
	for (int m = mm + 1; m <= 12; m++) {
		for (int d = 1; d <= dn[m]; d++) {
			string s = "";
			s += to_string(yy);
			if (m < 10) s += '0';
			s += to_string(m);
			if (d < 10) s += '0';
			s += to_string(d);
			int res = check(s);
			if (!f1 && res >= 1) {
				f1 = true;
				cout << s << endl;
			}

			if (!f2 && res == 2) {
				f2 = true;
				cout << s << endl;
			}
			if (f1 && f2) return 0;
		}
	}

	// 最后从 yy+1 年开始循环
	for (int y = yy + 1; y <= 9999; y++) {
		if (y % 4 == 0 && y % 100 != 0 || y % 400 == 0) dn[2] = 29;
		else dn[2] = 28;
		for (int m = 1; m <= 12; m++) {
			for (int d = 1; d <= dn[m]; d++) {
				string s = "";      // 将数字转换为字符串
				s += to_string(y);
				if (m < 10) s += '0';
				s += to_string(m);
				if (d < 10) s += '0';
				s += to_string(d);
				int res = check(s);
				if (!f1 && res >= 1) {
					f1 = true;
					cout << s << endl;
				}

				if (!f2 && res == 2) {
					f2 = true;
					cout << s << endl;
				}

				if (f1 && f2) return 0;
			}
		}
	}

	return 0;
}

蓝桥杯真题:回文日期2

  1. 枚举回文数
  2. 判断是否在范围内
  3. 再判断日期是否合法

整数构造回文串的方法

参考代码

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

using namespace std;

int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

bool check(int date)
{
    int day = date % 100;
    int month = date / 100 % 100;
    int year = date / 10000;
    
    if (year % 100 && year % 4 == 0 || year % 400 == 0) days[2] = 29;
    else days[2] = 28;  // 判断平年还是闰年
    
    if (month == 0 || month > 12 || day == 0 || day > days[month]) return false;
    return true;
}

int main()
{
    int date1 = 0, date2 = 0;
    cin >> date1 >> date2;
    
    int res = 0;
    for (int i = 1000; i < 10000; i++) {
        int date = i, x = i;
        // 将i反转并合并到date中,构造回文串
        for (int j = 0; j < 4; j++) date = date * 10 + x % 10, x /= 10;
        
        if (date1 <= date && date <= date2 && check(date)) res++;
    }
    
    cout << res << endl;
    return 0;
}

蓝桥杯真题:日期问题

scanf()的格式化输入

printf()的格式化输出

参考代码

#include<iostream>
#include<string>
#include<set>
#include<algorithm>

using namespace std;

const int N = 100010;

set<string> s;
int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

void check(int y, int m, int d)
{
	if (y >= 60) y += 1900;
	else y += 2000;
	if (y % 100 && y % 4 == 0 || y % 400 == 0) days[2] = 29;
	else days[2] = 28;
	if (m != 0 && m <= 12 && d != 0 && d <= days[m]) {
		string t = "";
		t += to_string(y);
		t += '-';
		if (m < 10) t += '0';
		t += to_string(m);
		t += '-';
		if (d < 10) t += '0';
		t += to_string(d);
        s.insert(t);
	}
}

int main()
{
	int a, b, c;
	scanf("%d/%d/%d", &a, &b, &c);

	// 年 月 日
	check(a, b, c);

	// 月 日 年
	check(c, b, a);

	// 日 月 年
	check(c, a, b);

	for (auto x : s) {
	    cout << x << endl;
	}
	return 0;
}

参考代码2

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;

int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

bool check(int year, int month, int day)
{
    if (year % 100 && year % 4 == 0 || year % 400 == 0) days[2] = 29;
	else days[2] = 28;
	if (month == 0 || month > 12 || day == 0 || day > days[month]) return false;
	
	return true;
}

int main()
{
	int a, b, c;
	scanf("%d/%d/%d", &a, &b, &c);
    
    // 枚举所有日期并判断是否合法
    for (int date = 19600101; date <= 20591231; date++) {
        int year = date / 10000, month = date / 100 % 100, day = date % 100;
        if (check(year, month, day)) {
            if (year % 100 == a && month == b && day == c ||
                month == a && day == b && year % 100 == c ||
                day == a && month == b && year % 100 == c )
                printf("%d-%02d-%02d\n", year, month, day);     // "%02d"位数不足2位补0
        }
    }
	
	return 0;
}

蓝桥杯真题:航班时间

sscanf(str.c_str, "%d:%d:%d %d:%d:%d (+%d)", &h1, &m1, &s1, &h2, &m2, &s2, &d);,具体见25行

参考代码

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

using namespace std;

const int N = 100010;

int n;

int getSecond(int h, int m, int s)  // 将 时:分:秒 转换为 秒数
{
	return h * 3600 + m * 60 + s;
}

int getTime()   // 读入时间并求出往返的时间差
{
	string line;
	getline(cin, line);

	if (line.back() != ')') line += " (+0)";    // 统一格式

	int h1, m1, s1, h2, m2, s2, d;
	sscanf(line.c_str(), "%d:%d:%d %d:%d:%d (+%d)", &h1, &m1, &s1, &h2, &m2, &s2, &d);

	return	getSecond(h2, m2, s2) - getSecond(h1, m1, s1) + d * 24 * 3600;
}
int main()
{
	scanf("%d", &n);

	string line;
	getline(cin, line);		// 忽略第一行末尾的回车

	while (n--) {
		int time = (getTime() + getTime()) / 2;
		int hour = time / 3600, minute = time % 3600 / 60, second = time % 60;
		printf("%02d:%02d:%02d\n", hour, minute, second);
	}

	return 0;
}

scanf()格式化输入,具体见第13行

#include<iostream>

using namespace std;

int getSecond(int h, int m, int s)	// 将 时:分:秒 转换为 秒数
{
    return h * 3600 + m * 60 + s;
}

int getTime()	// 读入时间并求出往返的时间差
{
    int h1,m1,s1,h2,m2,s2,d=0;
    scanf("%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d);
    int time = getSecond(h2, m2, s2) - getSecond(h1, m1, s1) + d * 24 * 3600;
    return time;
}
int main()
{
    int t;
    cin>>t;
    for(int i=0;i<t;i++)
    {
        int time = (getTime() + getTime()) / 2;
        printf("%02d:%02d:%02d\n",time / 3600, time / 60 % 60, time % 60);
    }
}

4.2 排序

蓝桥杯真题:外卖店优先级

模拟 + 排序

image-20250225151211501

参考代码

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

using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

int n, m, T;
int score[N], last[N];  // 分别表示第i个店铺的优先级和上一次有订单的时间
bool st[N];             // 记录店铺是否在优先缓存中

PII order[N];

int main()
{
    scanf("%d%d%d", &n, &m, &T);
    
    for (int i = 0; i < m; i++) scanf("%d%d", &order[i].first, &order[i].second);
    
    sort(order, order + m);     // 先按时间顺序,再按店铺id顺序排序
    
    for (int i = 0; i < m;) {
        // 处理t时刻之前的订单
        int j = i;  // 处理同一时刻同一店铺的cnt个订单
        while(j < m && order[j] == order[i]) j++;
        int t = order[i].first, id = order[i].second, cnt = j - i;
        i = j;
        score[id] -= t - last[id] - 1;
        if (score[id] < 0) score[id] = 0; 
        if (score[id] <= 3) st[id] = false;
        
        // 处理t时刻的订单
        score[id] += cnt * 2;
        if (score[id] > 5) st[id] = true;
        
        last[id] = t;   // 更新last
    }
    
    // 处理每个店铺直到最后的T时刻没有订单的情况
    for (int i = 1; i <= n; i++) {
        score[i] -= T - last[i];
        if (score[i] <= 3) st[i] = false;
    }
    
    // 遍历优先级统计出答案
    int res = 0;
    for (int i = 1; i <= n; i++) {
        res += st[i];
    }
        
    printf("%d\n", res);
}

第五章 树状数组与线段树

第六章 BFS与图论

image-20250226195549384

6.1 双指针

蓝桥杯真题:日志统计

pair存储数据{ id, ts },然后按id和时间排序,用双指针ij指向区间$[T, T + D)$,判断ij - 1是否是同一id且相差时间是否小于D,然后ij同时向右移动判断。

时间复杂度:$O(N)$

参考代码

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

using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

int n, d, k;
PII Log[N];             // 存储日志信息 { id, ts }
vector<int> hot;        // 存储热帖

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

	for (int i = 0; i < n; i++) {
		int ts, id;
		scanf("%d%d", &ts, &id);
		Log[i] = { id, ts };
	}

	sort(Log, Log + n);		// 先按帖子排序,再按时间排序
    
	int cnt = 0;
	for (int i = 0, j = k - 1; i < n, j < n; ) {    // 找k个日志
	    // 判断是否是同一个id并且相差时间是否小于D
		if (Log[i].first == Log[j].first && Log[j].second - Log[i].second < d) {
			hot.push_back(Log[i].first);    // 放入热帖列表
			i = j;                          // 寻找下一个帖子
			while (i < n && Log[i].first == Log[j].first) i++;
			j = i + k - 1;
		}
		else {
		    i ++;
		    j ++;
		}
	}
    
    // 输出热帖id
	for (int i = 0; i < hot.size(); i++) cout << hot[i] << endl;

	return 0;
}

蓝桥杯真题:完全二叉树的权值

简单题,一发AC

时间复杂度:$O(N)$

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

using namespace std;
typedef long long ll;

const int N = 100010;

int n;

int main()
{
	scanf("%d", &n);
	int i = 1, h = 1, cnt = 1;
	ll sums = -100000000000;
	int ans = 1;
	while (i <= n) {
		ll s = 0;
		for (int j = 0; j < cnt; j++) {
			int t;
			scanf("%d", &t);
			s += t;
			i++;
			if (i > n) break;
		}
		if (s > sums) sums = s, ans = h;
		cnt *= 2;
		h++;
	}

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

	return 0;
}

6.2 BFS

BFS模板

image-20250227194135803

AcWing 1101. 献给阿尔吉侬的花束

简单BFS求最短路

参考代码

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

using namespace std;

typedef pair<int, int> PII;

const int N = 210;

int n, m;
char g[N][N];
int dist[N][N];
int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };	// 上右下左

bool bfs(int a, int b)
{
    queue<PII> q;   
	// 初始化
	dist[a][b] = 0;
	q.push({ a, b });

	while (!q.empty()) {
		PII t = q.front();
		q.pop();
		for (int i = 0; i < 4; i++) {
			int x = t.first + dx[i], y = t.second + dy[i];
			if (x >= 0 && x < n && y >= 0 && y < m && dist[x][y] == -1 && g[x][y] != '#') {
				dist[x][y] = dist[t.first][t.second] + 1;
				q.push({ x, y });
				if (g[x][y] == 'E') return true;		// 找到奶酪
			}
		}
	}
	
	return false;
}

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

	while (t--) {
		memset(dist, -1, sizeof dist);
		scanf("%d%d", &n, &m);

		int x = 0, y = 0;	// 记录老鼠的初始位置
		int xe = 0, ye = 0;	// 记录奶酪的位置

		for (int i = 0; i < n; i++) scanf("%s", g[i]);

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if (g[i][j] == 'S') x = i, y = j;
				if (g[i][j] == 'E') xe = i, ye = j;
			}
		}

		if (bfs(x, y)) cout << dist[xe][ye] << endl;
		else cout << "oop!" << endl;
	}

	return 0;
}

AcWing 1096. 地牢大师

简单题,BFS + 三维数组,套路跟二维数组相同,注意每次需要清空队列!可以在bfs()内创建队列。

另外对于三元数据的存储,我这里使用的是嵌套pairpair<int, pair<int, int>>存储的,更建议使用struct结构体存储。

参考代码1(嵌套pair)

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

using namespace std;
typedef long long ll;
typedef pair<int, pair<int, int>> PIII;

const int N = 110;

int l, r, c;
char g[N][N][N];
int dist[N][N][N];
int dx[6] = { -1, 0, 1, 0, 0, 0 }, dy[6] = { 0, 1, 0, -1, 0, 0 }, dz[6] = { 0, 0, 0, 0, 1, -1 };

void bfs(int x, int y, int z)
{
	queue<PIII> q;
	dist[z][x][y] = 0;
	q.push({ z, {x, y } });

	while (!q.empty()) {
		auto t = q.front(); q.pop();
		int tz = t.first, tx = t.second.first, ty = t.second.second;
		for (int i = 0; i < 6; i++) {
			int z = tz + dz[i], x = tx + dx[i], y = ty + dy[i];
			if (z >= 0 && z < l && x >= 0 && x < r && y >= 0 && y < c && dist[z][x][y] == -1 && g[z][x][y] != '#') {
				dist[z][x][y] = dist[tz][tx][ty] + 1;
				q.push({ z, {x, y} });
				if (g[z][x][y] == 'E') return;
			}
		}
	}
}

int main()
{
	while (1) {
		memset(dist, -1, sizeof dist);
		scanf("%d%d%d", &l, &r, &c);
		if (!l && !r && !c) break;

		for (int i = 0; i < l; i++) {
			for (int j = 0; j < r; j++) {
				scanf("%s", g[i][j]);
			}
			char hang[110];
			scanf("%c", hang);
		}

		// 找出起点和终点
		int sx = 0, sy = 0, sz = 0, ex = 0, ey = 0, ez = 0;
		for (int i = 0; i < l; i++) {
			for (int j = 0; j < r; j++) {
				for (int k = 0; k < c; k++) {
					if (g[i][j][k] == 'S') sz = i, sx = j, sy = k;
					if (g[i][j][k] == 'E') ez = i, ex = j, ey = k;
				}
			}
		}

		bfs(sx, sy, sz);

		if (dist[ez][ex][ey] == -1) printf("Trapped!\n");
		else printf("Escaped in %d minute(s).\n", dist[ez][ex][ey]);
	}

	return 0;
}

参考代码2(struct)

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

using namespace std;

const int N = 110;

// 定义三维坐标结构体
struct Pos {
    int z, x, y;  // 层、行、列
};

int l, r, c;
char g[N][N][N];
int dist[N][N][N];
int dx[6] = { -1, 0, 1, 0, 0, 0 }, dy[6] = { 0, 1, 0, -1, 0, 0 }, dz[6] = { 0, 0, 0, 0, 1, -1 };

void bfs(int start_x, int start_y, int start_z) {
    queue<Pos> q;
    dist[start_z][start_x][start_y] = 0;
    q.push({ start_z, start_x, start_y });  // 使用emplace直接构造

    while (!q.empty()) {
        auto t = q.front(); q.pop();

        // 直接访问结构体成员
        for (int i = 0; i < 6; i++) {
            int z = t.z + dz[i], x = t.x + dx[i], y = t.y + dy[i];

            if (z >= 0 && z < l && x >= 0 && x < r && y >= 0 && y < c
                && dist[z][x][y] == -1 && g[z][x][y] != '#') {

                dist[z][x][y] = dist[t.z][t.x][t.y] + 1;
                q.push({ z, x, y });  // 压入新坐标

                if (g[z][x][y] == 'E') return;
            }
        }
    }
}

int main() {
    while (true) {
        memset(dist, -1, sizeof dist);
        scanf("%d%d%d", &l, &r, &c);
        if (!l && !r && !c) break;

        // 读取输入
        for (int i = 0; i < l; i++) {
            for (int j = 0; j < r; j++) {
                scanf("%s", g[i][j]);
            }
            getchar();  // 读取换行符
        }

        // 查找起点终点
        Pos start = { 0, 0, 0 }, end = { 0, 0, 0 };
        for (int i = 0; i < l; i++) {
            for (int j = 0; j < r; j++) {
                for (int k = 0; k < c; k++) {
                    if (g[i][j][k] == 'S') start = { i, j, k };
                    if (g[i][j][k] == 'E') end = { i, j, k };
                }
            }
        }

        bfs(start.x, start.y, start.z);

        if (dist[end.z][end.x][end.y] == -1) {
            printf("Trapped!\n");
        }
        else {
            printf("Escaped in %d minute(s).\n", dist[end.z][end.x][end.y]);
        }
    }
    return 0;
}

蓝桥杯:全球变暖

简单Flood fill

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
//#include<bits/stdc++.h>

using namespace std;

const int N = 1010;
typedef long long LL;
typedef pair<int, int> PII;

int n;
char g[N][N];
bool st[N][N];
int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 };

bool bfs(int a, int b)
{
	bool res = false;
	queue<PII> q;
	q.push({ a, b });
	st[a][b] = true;

	while (!q.empty()) {
		auto t = q.front(); q.pop();
		int fx = 0;
		for (int i = 0; i < 4; i++) {
			int x = t.first + dx[i], y = t.second + dy[i];
			if (x < 0 or x >= n or y < 0 or y >= n) {
			    fx++;
			}
			else if (x >= 0 && x < n && y >= 0 && y < n && g[x][y] == '#') {
				fx++;
				if (!st[x][y]) {
					st[x][y] = true;
					q.push({ x, y });
				}
			}
		}
		if (fx == 4) res = true;
	}

	return res;
}

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

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

	memset(st, false, sizeof st);

	int cnt = 0, ans = 0;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (g[i][j] == '#' && !st[i][j]) {
				cnt++;  // 原岛屿数量
				if (bfs(i, j))
					ans++;  // 未被完全淹没的岛屿数量
			}
		}
	}

	printf("%d\n", cnt - ans);

	return 0;
}

6.3 图论

蓝桥杯:交换瓶子

算法一:暴力枚举
1、通过观察可以发现,我们每一个数都必须回到它自己的位置上,比如 1 必须在第一位,2 必须在第二位上

2、那么我们就可以这样操作,由于每个数必须回到自己的位置,直接从 1 枚举到 n ,如果当前位置的数不等于它的下标,那么我们就必须要把它给替换掉

3、设当前位置为 i 的话,那么我们就从 i+1 开始往后枚举,直到找到对应的 a[j] 和我们的 i 相等,那么我们就把上个数交换,交换次数加一

4、容易证明这个算法的正确性,由于每个数必须回到原来的位置,所以我们这样子操作不会出现多于的步骤,因为每次操作都是必须的。

时间复杂度:$O(n^2)$

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

using namespace std;

const int N=10010;

int n;

int a[N];

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

    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        if(a[i]!=i)//直接遍历,如果不是自身的话,我们必然要交换,所以不会出现多于的操作
        {
            for(int j=i+1;j<=n;j++)
            if(a[j]==i)
             swap(a[i],a[j]);

             sum++;
        }
    }
    
    printf("%d\n",sum);
    
    return 0;

}

算法二:置换群

image-20250322173159781

时间复杂度:$O(N)$

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

using namespace std;

const int N = 10010;

int n;
int b[N];
bool st[N];		// 判重,帮助找环

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &b[i]);

	int cnt = 0;	// 记录环的数量
	for (int i = 1; i <= n; i++) {
		if (!st[i]) {	// 如果该点没有被遍历过,说明它属于一个新的环
			cnt++;	// 环数+1
			for (int j = i; !st[j]; j = b[j]) {
				st[j] = true;	// 将这个新环的所有数标记
			}
		}
	}

	printf("%d\n", n - cnt);

	return 0;
}

蓝桥杯:大臣的旅费

由”同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的“可知,该图是无环图,且从上面可知是无环连通图,即

该问题属于第一种,求树的直径问题。

树形DP常见问题类型

  1. 树的直径:最长路径问题,每个节点维护最大和次大深度。
  2. 最大独立集:如“没有上司的舞会”,选择不相邻节点使总和最大。
  3. 最小顶点覆盖:选择最少节点覆盖所有边。
  4. 树的重心:删除节点后使最大连通块最小化。
  5. 路径和问题:如二叉树中的最大路径和。

image-20250405162054918

具体做法:

  1. 在树中任取一个点m,找到距离m最远的点x(则点x必定是直径的端点之一)
  2. 找到距离点x最远的点y,则点x和点y即为该树的直径之一的两个端点。

时间复杂度:$O(N)$

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

using namespace std;

const int N = 100010;

int n;
struct Edge
{
	int id, w;	// 邻接点和边的权值
};

vector<Edge> h[N];		// 邻接表存储图,表头h
int dist[N];            // dist[u] 表示点u到起点的距离


void dfs(int u, int father, int distance)	// 当前点u,上一个点father(避免回溯),当前的距离distance
{
	dist[u] = distance;
	for (auto node : h[u]) {
		if (node.id != father) {
			dfs(node.id, u, distance + node.w);	// 递归子节点,累加距离
		}
	}
}


int main()
{
	scanf("%d", &n);
	for (int i = 0; i < n - 1; i++) {
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		h[a].push_back({ b, c });	// 双向添加边
		h[b].push_back({ a, c });
	}

	dfs(1, -1, 0);	// 从点1开始遍历所有点,求出每个点到起点1的距离存到dist中

	int x = 1;	// 找到距离起点最远的点x,x为该树的直径的端点之一
	for (int i = 1; i <= n; i++) {
		if (dist[i] > dist[x]) {
			x = i;
		}
	}

	dfs(x, -1, 0);  // 从距离起点1最远的点x开始,求出所有点到点x的距离,并存储到dist中
    
    int y = 1;  // 找到距离点x最远的点y,则点x和点y为该树的直径之一的两个端点
	for (int i = 1; i <= n; i++) {
		if (dist[i] > dist[y])
			y = i;  // 更新y为距离x最远的点
	}

	int s = dist[y];    // 此时的dist[y]则为树的直径,即任意两个点的最大距离

	printf("%lld\n", 10 * s + s * (s + 1ll) / 2);   // 由公式计算出结果

	return 0;
}

第七章 贪心

image-20250405202942803

AcWing 1055. 股票买卖II

简单贪心

时间复杂度:$O(N)$


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

using namespace std;

const int N = 100010;
typedef long long LL;

int n;
int prices[N];

int main()
{
	scanf("%d", &n);
	for (int i = 0; i < n; i++) scanf("%d", &prices[i]);

	int sums = 0;
	for (int i = 0; i + 1 < n; i++) {
		int dp = prices[i + 1] - prices[i];
		if (dp > 0) sums += dp;
	}

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

	return 0;
}

AcWing 104. 货仓选址

WA了一次,第一想法是把仓库建在最左端和最右端的商铺的正中间,通过了样例就交了……

正确做法是用仓库把所有商店一分为二,即仓库左边的商店数量和右边的商店数量要尽可能相同。

  1. 从最简单的情况想:如果只有一个商店,最优解肯定是仓库和商店位置相同,距离为0
  2. 如果有2个商店(从左到右位置分别是a和b, b > a),最优解是建在区间[a, b]中,距离之和为b - a
  3. 如果有3个商店(从左到右位置分别是a, b, c),则最优解是仓库和中间商店的位置相同,距离之和为c - a
  4. 依次类推可得出:
    • 有奇数个商店时,设为n个,将所有商店按位置排序后,则应在第 $(n+1)/2$个商店上
    • 有偶数个商店时,设为n个,将所有商店按位置排序后,则应在第$n/2$个商店上

数学证明

image-20250405212440437

参考代码

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

using namespace std;

const int N = 100010;
typedef long long LL;

int n;
int a[N];

int main()
{
	scanf("%d", &n);
	for (int i = 0; i < n; i++) scanf("%d", &a[i]);

	sort(a, a + n);

	int ans = 0, mid = a[n / 2];
	for (int i = 0; i < n; i++) {
		ans += abs(a[i] - mid);
	}

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

	return 0;
}

时间复杂度:$O(nlogn)$

AcWing 122. 糖果传递

image-20250405220829154

image-20250405232945384

image-20250405233043508

image-20250405233216939

参考代码

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

using namespace std;

const int N = 1000010;
typedef long long LL;

int n;
int a[N];
LL c[N];

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

	LL sums = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		sums +=  a[i];
	}

	LL avg = sums / n;
	for (int i = n; i > 1; i--) {
		c[i] = c[i + 1] + avg - a[i];
	}
	c[1] = 0;

	sort(c + 1, c + n + 1);

	LL ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += abs(c[i] - c[(i + 1) / 2]);
	}

	printf("%lld\n", ans);

	return 0;
}

时间复杂度:$O(NlogN)$,可以用快速选择优化到$O(N)$

AcWing 112. 雷达设备

image-20250408190606116

处理:将每个小岛可以选择的雷达坐标转换为一个区间

将原问题等价为给定n个区间,求最少选多少个点,可以使得每个区间上最少被选一个点

排序方式 时间复杂度 适用场景 优势
端点排序 $O(n log n)$ 最小覆盖点问题 保证最优解
端点排序 $O(n log n)$ 最大不相交区间 不适用覆盖问题

这种排序方式是解决区间覆盖类问题的标准方法,正确性已被算法理论严格证明。

参考代码

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

using namespace std;
typedef long long LL;
typedef pair<double, double> PDD;

const int N = 1010;

int n;
PDD a[N];

int main()
{
	int d;
	scanf("%d%d", &n, &d);
	
	bool more = false;      // 判断是否超出雷达的所有可能范围
	for (int i = 0; i < n; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		int t = abs(y);
		if (t > d) more = true;
		else {  // 如果没有超出可能范围,则将其转换为一段可能的区间
			double di = sqrt(d * d - t * t);
			a[i] = {x - di, x + di};
		}
	}
	
	if (more) {
	    printf("-1");
	    return 0;
	}
	
	// 按右端点降序排列
	sort(a, a + n, [](PDD x, PDD y){return x.second < y.second;});
	
	int ans = 0;    // 枚举每个区间
	for (int i = 0; i < n;) {
	    ans++;      // 更新雷达数
		double r = a[i].second; // 将雷达放到最右端(贪心策略),保证最优解 
		int j = i + 1;          // 枚举后面的区间
		while(j < n && a[j].first <= r) j++;    // 如果区间j已经被覆盖,则跳过
		i = j;                  // 更新需要枚举的区间
	}
	
	printf("%d", ans);      // 输出答案
	
	return 0;
} 

时间复杂度:$O(NlogN)$

蓝桥杯真题:付账问题

贪心问题

为什么要排序?
如果不排序,会存在较大的 a[i] 先被处理,可能被设为较低的初始平均值,导致后续较小的 a[i] 无法调整,从而可能导致无法付清全部的账单,计算出错误的方差。

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


using namespace std;
typedef long long LL;

const int N = 5e5 + 10;

int n;
double a[N];
double b[N];

void solve()
{
	double S;
	scanf("%d%lf", &n, &S);

	for (int i = 1; i <= n; i++) {
		scanf("%lf", &a[i]);
	}
	
	sort(a + 1, a + n + 1);

	double n_avg = S / n, sums = S;
	for (int i = 1; i <= n; i++) {
		if (a[i] >= n_avg) {
			b[i] = n_avg;
			sums -= b[i];
		}
		else {
			b[i] = a[i];
			sums -= b[i];
			n_avg = sums / (n - i);
		}
	}

	// 计算方差
	double ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += (b[i] - S / n) * (b[i] - S / n);
	}

	ans = sqrt(ans / (n * 1.0));

	printf("%.4f\n", ans);
}


int main()
{
	int T = 1;
	//cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

蓝桥杯真题:乘积最大

贪心+分类讨论

image-20250519182700977

参考代码$O(nlogn)$

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

using namespace std;
typedef long long LL;

const int N = 1e5 + 10, Mod = 1000000009;

int n, k;
int a[N];

void solve()
{
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> a[i];
	sort(a, a + n);

	int res = 1;
	int l = 0, r = n - 1;
	int sign = 1;
	if (k % 2) {
		res = a[r--];
		k--;
		if (res < 0) sign = -1;		// 说明全是负数,否则说明至少存在一个整数,取完最大的数之后变为k为偶数的情况
	}

	while (k) {
		LL x = (LL)a[l] * a[l + 1], y = (LL)a[r - 1] * a[r];
		if (x * sign > y * sign) {  // 这个太妙了,全负数就选最小值
			res = x % Mod * res % Mod;
			l += 2;
		}
		else {
			res = y % Mod * res % Mod;
			r -= 2;
		}

		k -= 2;
	}

	cout << res << endl;
}

int main()
{
	int T = 1;
	//cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

蓝桥杯真题:后缀表达式

后缀表达式对应一个二叉树的后序遍历,同理,前缀表达式中缀表达式分别对应二叉树的前序遍历中序遍历

特别的,中缀表达式即为最常用的“a + b”形式。

其中,前缀表达式又称波兰式后缀表达式又称逆波兰式

分析这题可知,如果正负号的个数都大于0,通过调整正负号的位置,可以改变正负号的个数。

更具体的说,

  • M=0时,则全部为加号,
  • M>0时,则负号的个数可调整为1~M+N

根据贪心策略,尽量减掉负数,加上正数。如果不够,那么尽量减掉较小的正数,加上较大的负数。

参考代码 $O(nlogn)$

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

using namespace std;
typedef long long LL;

const int N = 2e5 + 10, Mod = 1000000009;

int n, m;
int a[N];

void solve()
{
	cin >> n >> m;

	int cnt = 0;
	for (int i = 0; i < n + m + 1; i++) {
		cin >> a[i];
		if (a[i] < 0) cnt++;
	}
	
	sort(a, a + n + m + 1);		// 也可以不排序,找出最大值最小值即可,不排序是O(n)

	LL ans = 0;
	if (m == 0) {
		for (int i = 0; i < n + m + 1; i++) ans += a[i];
	}
	else {
		if (cnt == 0) {         // 全正,且负号的个数为 1 ~ n+m 
			ans -= a[0];
			for (int i = 1; i < n + m + 1; i++) ans += a[i];
		}
		else if (cnt == n + m + 1) {    // 全负,且负号的个数为 1 ~ n+m
			ans = a[n + m] - a[n + m - 1];
			for (int i = 0; i < n + m - 1; i++) ans += abs(a[i]);
		}
		else {                  // 至少有一个正数
			for (int i = 0; i < n + m + 1; i++) ans += abs(a[i]);
		}
	}

	cout << ans << endl;
}


int main()
{
	int T = 1;
	//cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

蓝桥杯真题:灵能传输

第八章 数论

image-20250519203414309

AcWing 1246. 等差数列

简单题,考察最大公约数

根据公式 $a_n = a_1 + (n-1)*d$,
可以得出 $n = (a_n - a_1) / d + 1$,即公差d越大,项数n越小。

当$a_n$最小,且$a_1$最大,且$d$最大时,项数n最小。

先将数列a排序,然后求出相邻两项的差的公共最大公约数,这个公共最大公约数就是这个数列的最大的公差d。

注意特判d == 0的情况!!

参考代码 $O(nlogn)$

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

using namespace std;
typedef long long LL;

const int N = 1e5 + 10, Mod = 1000000009;

int n;
int a[N];

// 辗转相除法求最大公约数
int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}

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

	sort(a, a + n);

	int d = a[0] - a[1];
	for (int i = 1; i < n - 1; i++) {
		int t = a[i + 1] - a[i];
		d = gcd(d, t);	// 求出相邻两项的差的公共最大公约数
        if(d == 1) break;
	}

	int ans = 0;
	if (d == 0) ans = n;	// 注意特判d为0
	else {
		ans = (a[n - 1] - a[0]) / d + 1;	// 公式推得
	}

	cout << ans << endl;
}


int main()
{
	int T = 1;
	//cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

AcWing 1295. X的因子链

image-20250523194414724

线性筛+分解质因数+组合计数

参考代码$O(n+m)$

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

using namespace std;
typedef long long LL;
const int N = (1 << 20) + 10;


// 线性筛质数
int primes[N], cnt;		// 存所有质数
int st[N];				// 当前数有没有被筛过(是否是某个数的倍数)
int minp[N];			// 存primes[i]的最小质因子

void get_primes(int n)
{
	for (int i = 2; i <= n; i++) {
		if (!st[i])	minp[i] = i, primes[cnt++] = i;
		for (int j = 0; primes[j] * i <= n; j++) {
			st[primes[j] * i] = true;	// 以质数primes[j]为倍数的所有数都筛掉
			minp[primes[j] * i] = primes[j];
			if (i % primes[j] == 0) break;		// 保证每个合数都是被其最小质因子筛掉
		}
	}
}

int main()
{
    get_primes(N - 1);
    
    int fact[30], sum[N];   // 分别记录x的质因数和每个质因数的指数
    int x;
    while(cin >> x) {
        int k = 0, tot = 0;  // x的因子个数,总的指数的和
        while(x > 1) {
            int p = minp[x];    // 取出x的最小质因子
            fact[k] = p, sum[k] = 0;	// 分解质因数
            while(x % p == 0) {
                x /= p;
                sum[k]++;
                tot++;
            }
            k++;
        }
        LL res = 1;
        for (int i = 1; i <= tot; i++) res *= i;    // 计算tot的阶乘
        for (int i = 0; i < k; i++) {
            for (int j = 1; j <= sum[i]; j++) {
                res /= j;       // 除以每个质因子的指数的阶乘
            }
        }
        cout << tot << ' ' << res << endl;
    }
    return 0;
}

AcWing 1296. 聪明的燕姿

约数之和,见算法基础课数论部分。

image-20250523213008994

参考代码(太难了看不懂!)

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

using namespace std;
typedef long long LL;

const int N = 50000;


// 线性筛质数
int primes[N], cnt;		// 存所有质数
int st[N];				// 当前数有没有被筛过(是否是某个数的倍数)
int ans[N], len;		// 记录答案和答案个数

void get_primes(int n)
{
	for (int i = 2; i <= n; i++) {
		if (!st[i]) primes[cnt++] = i;
		for (int j = 0; primes[j] * i <= n; j++) {
			st[primes[j] * i] = true;	// 以质数primes[j]为倍数的所有数都筛掉
			if (i % primes[j] == 0) break;		// 保证每个合数都是被其最小质因子筛掉
		}
	}
}

bool is_prime(int x)	// 判断质数
{
	if (x < N) return !st[x];
	for (int i = 0; primes[i] <= x / primes[i]; i++) {
		if (x % primes[i] == 0) return false;
	}
	return true;
}

void dfs(int last, int prod, int s)		//(上一个枚举到的质数下标,当前的数,当前剩余的数)
{
	if (s == 1) {	// 终止条件:剩余s为1,记录答案
		ans[len++] = prod;
		return;
	}

	// 处理s-1为质数的特殊情况
	if (s - 1 > (last < 0 ? 1 : primes[last]) && is_prime(s - 1))
		ans[len++] = prod * (s - 1);	

	// 枚举质数,尝试分解s
	for (int i = last + 1; primes[i] <= s / primes[i]; i++) {
		int p = primes[i];
		for (int j = 1 + p, t = p; j <= s; t *= p, j += t) {
			if (s % j == 0) dfs(i, prod * t, s / j);	// 递归分解剩余部分
		}
	}
}

void solve()
{
	get_primes(N - 1);

	int s;
	while (cin >> s) {
		len = 0;
		dfs(-1, 1, s);

		cout << len << endl;
		if (len) {
			sort(ans, ans + len);
			for (int i = 0; i < len; i++) cout << ans[i] << ' ';
			cout << endl;
		}
	}
}

int main()
{
	int T = 1;
	//cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

AcWing 1299. 五指山

扩展欧几里得算法(裴蜀定理)

image-20250524201826394

image-20250524203112081

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

using namespace std;

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

int main()
{
    int a, b, x, y;
    cin >> a >> b;
    int d = exgcd(a, b, x, y);
    
    printf("%d * %d + %d * %d = %d\n", a, x, b, y, d);
    
    return 0;
}

本题,有 $x + bd \equiv y \pmod{n}$

即 $x + bd \equiv y + an$

​ $ -an + bd \equiv y - x$

其中的n、d、y、x均为给定值,根据裴蜀定理,$ax + by = gcd(a, b)$,故当且仅当$gcd(n, d)|(y - x)$时有解,否则无解(输出Impossible)。

如果有解,设解为$a', b'$,

则有$a' * n + b' * d = gcd(n, d)$(裴蜀定理),

且$y - x = k * gcd(n, d)$(否则无解),

故特解$b_0 = k * b’$,

即$b_0 = \frac{y-x}{gcd(n, d)} * b'$

找到特解$b_0$后,需找到$b$的最小值。

由于在线性同余方程中,通解$b$与特解$b_0$有如下关系:
$\boldsymbol{b = b_0 + t * \frac{n}{gcd(n, d)}, t \in \mathbb{Z}}$,(t属于整数集Z)

记$n' = \frac{n}{gcd(n, d)}$,要想求$b$的最小非负整数解$b_m$,

则可视为$b_0 = bm + k * n'$,

则最小非负整数解$\boldsymbol{b_m = {(b_0 % n') + n'} % n'}$

image-20250524184204194

参考代码$O(Tlog(min(n,d)))$

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

using namespace std;
typedef long long LL;

const int N = 50000;

// 扩展欧几里得定理
LL exgcd(LL a, LL b, LL &x, LL &y)
{
	if (b == 0) {
		x = 1, y = 0;
		return a;
	}

	LL d = exgcd(b, a % b, y, x);
	y -= a / b * x;

	return d;
}


void solve()
{
	LL n, d, x, y, a, b;
	cin >> n >> d >> x >> y;

	int gcd = exgcd(n, d, a, b);	// 求出gcd(n, d),以及裴蜀定理的a, b,使得an + bd = gcd(n, d)

	if ((y - x) % gcd) cout << "Impossible" << endl;	// 如果(y - x) | gcd(n, d)
	else {
		b *= (y - x) / gcd;			// 由b'扩大(y-x)/gcd(n,d)倍得到特解b0
		n /= gcd;					// n' = n / gcd(n, d)
		cout << (b % n + n) % n << endl;	// 求出b的最小非负整数解
	}
}


int main()
{
	int T = 1;
	cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

蓝桥杯真题:最大比例

数论+辗转相除法+更相减损术

参考代码(好难,看不懂)

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

using namespace std;
typedef long long LL;

const int N = 110;

int n;
LL x[N], a[N], b[N];

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

// 更相减损术
LL gcd_sub(LL a, LL b)
{
	if (a < b) swap(a, b);
	if (b == 1) return a;
	return gcd_sub(b, a / b);
}

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

	sort(x, x + n);
	
	int cnt = 0;
	for (int i = 1; i < n; i++) {
		if (x[i] != x[i - 1]) {
			LL d = gcd(x[i], x[0]);
			a[cnt] = x[i] / d;
			b[cnt++] = x[0] / d;
		}
	}

	LL up = a[0], down = b[0];
	for (int i = 1; i < cnt; i++) {
		up = gcd_sub(up, a[i]);
		down = gcd_sub(down, b[i]);
	}

	cout << up << '/' << down << endl;
}


int main()
{
	int T = 1;
	//cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

AcWing 1301. C循环

跟五指山这道题一模一样

因为是k位存储系统,因此每个数都相当于是模上$2^{k}$的结果,因此,该题是求线性同余方程

$A + xC \equiv B \pmod{2^{k}}$

求出$x$的最小非负整数解。

参考代码

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

using namespace std;
typedef long long LL;

const int N = 110;

int n, m;

LL exgcd(LL a, LL b, LL& x, LL& y)
{
	if (b == 0) {
		x = 1, y = 0;
		return a;
	}

	LL g = exgcd(b, a % b, y, x);
	y -= a / b * x;

	return g;
}

void solve()
{
	LL a, b, c, k, x, y;
	while (1) {
		cin >> a >> b >> c >> k;
		if (!a && !b && !c && !k) break;

		LL kk = 1LL << k;

		LL gcd = exgcd(c, kk, x, y);

		if ((b - a) % gcd) cout << "FOREVER" << endl;
		else {
			x *= (b - a) / gcd;
			kk /= gcd;
			cout << ((x % kk) + kk) % kk << endl;
		}
	}
}


int main()
{
	int T = 1;
	//cin >> T;

	while (T--) {
		solve();
	}

	return 0;
}

蓝桥杯真题:正则问题

递归、DFS

image-20250528211712570

参考代码

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

using namespace std;

int k;
string str;

int dfs()
{
    int res = 0;
    
    while(k < str.size()) {
        if (str[k] == '(') {     // 处理 (......)
            k++;    // 跳过 '('
            res += dfs();
            k++;    // 跳过 ')'
        }
        else if (str[k] == '|') {
            k++;    // 跳过 '|'
            res = max(res, dfs());
        }
        else if (str[k] == ')') break;
        else {
            k++;    // 跳过 'x'
            res++;  // 统计 'x'的数量
        }
    }
    
    return res;
}

int main()
{
    cin >> str;
    cout << dfs() << endl;
    
    return 0;
}

※蓝桥杯真题:糖果

经典DFS——重复覆盖问题,即用最少的行覆盖所有列。与之对应的另一个经典DFS问题是精准覆盖问题

经典优化:

  1. 迭代加深 (位运算)
  2. 优先枚举选择最少得列
  3. 可行性剪枝

image-20250529180649498

核心思路

  1. 问题建模
    将每行视为一个集合,元素为列。目标是用最少的行覆盖所有列。
  2. 状态压缩
    用二进制位表示列覆盖状态(例如,1 << c 表示第 c 列被覆盖)。
  3. IDA*算法
    结合迭代加深(Iterative Deepening)和启发式剪枝(A*)快速找到最优解。

A*和IDA*的区别

  • A* 像专业导航
    同时分析所有可能路线,选择最优路径,但需大内存“记录所有地图”。
  • IDA* 像渐进式探索
    先尝试“5分钟能到”的路线,失败后尝试“10分钟能到”的路线,只记当前路径。

参考代码(好难!!看不懂!!!)

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

using namespace std;

const int N = 110, M = 1 << 20;

int n, m, k;
vector<int> col[N];
int lg2[M];

int lowbit(int x)   // 返回 x 的二进制表示中最低位的 1 对应的值
{
    return x & -x;
}

int h(int state)    // 估算从当前状态 state 覆盖所有列所需的最少行数
{
    int res = 0;
    // 计算未覆盖的列:i = (1 << m) - 1 - state,
    for (int i = (1 << m) - 1 - state; i; i -= lowbit(i)) {
        int c = lg2[lowbit(i)];     // 遍历未覆盖的列(通过 lowbit 逐个取出)
        res++;
        for (auto row : col[c]) i &= ~row;
    }

    return res;
}

bool dfs(int depth, int state)
{
    // 剩余深度为 0,或启发式函数 h(state) 超过剩余深度
    if (depth == 0 || h(state) > depth) return state == (1 << m) - 1;

    //找到选择性最少的一列
    int t = -1; // 用m个1减去当前的状态剩下的则为未选择的1
    for (int i = (1 << m) - 1 - state; i; i -= lowbit(i)) {
        int c = lg2[lowbit(i)];
        if (t == -1 || col[t].size() > col[c].size()) {
            t = c;
        }
    }

    // 枚举选哪一行
    for (auto row : col[t])
        if (dfs(depth - 1, state | row))
            return true;

    return false;
}

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

    for (int i = 0; i < m; i++) lg2[1 << i] = i;    // 预处理 lg2 数组,加速二进制位的索引查询
    for (int i = 0; i < n; i++) {
        int state = 0;
        for (int j = 0; j < k; j++) {
            int c;
            cin >> c;
            state |= 1 << c - 1;    // 将每行转换为二进制状态
        }

        for (int j = 0; j < m; j++) {
            if (state >> j & 1) {       // 记录每列被哪些行覆盖
                col[j].push_back(state);
            }
        }
    }

    for (int i = 0; i < m; i++)
    {
        // 对每列的行去重并排序,减少无效搜索
        sort(col[i].begin(), col[i].end());
        col[i].erase(unique(col[i].begin(), col[i].end()), col[i].end());
    }

    int depth = 0;  // 递归层数
    while (depth <= m && !dfs(depth, 0)) depth++;   // 从 depth = 0 开始逐步增加深度限制,调用 dfs 寻找最小解

    if (depth > m) depth = -1;
    cout << depth << endl;

    return 0;
}

第九章 复杂DP

image-20250529191505651

AcWing 1050. 鸣人的影分身

经典的DP问题——整数划分问题

image-20250529202450695

参考代码$O(n^{2})$

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

using namespace std;

const int N = 1010;

int n, m;
int f[N][N];   // f[i][j]表示所有总和是i且被分成j个数的和的方案数

void solve()
{
    memset(f, 0, sizeof f);
    cin >> m >> n;
    
    for (int i = 0; i <= n; i++) f[0][i] = 1;   // 处理初始值
    for (int i = 1; i <= m; i++) {      // 枚举和
        for (int j = 1; j <= n; j++) {  // 枚举数字个数
            f[i][j] = f[i][j - 1];      // 状态转移
            if (i >= j) f[i][j] = f[i][j - 1] + f[i - j][j];
        }
    }
    
    cout << f[m][n] << endl;
}

int main()
{
    int t;
    cin >> t;
    while(t--) {
        solve();
    }
    
    return 0;
}

AcWing 1047. 糖果

动态规划的一般状态表示为二维的f[i][j],其中第一维i表示数量,第二维j表示约束条件。而f[i][j]的值一般表示所有选法的最大值、最小值或者数量

image-20250603200925854

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

using namespace std;

const int N = 110;

int n, k;
int f[N][N];

int main()
{
    cin >> n >> k;
    
    memset(f, -0x3f, sizeof f); // 初始化为负的0x3f3f3f,约为-1e9
    f[0][0] = 0;    // 初始状态
    for (int i = 1; i <= n; i++) {
        int w;
        cin >> w;
        for (int j = 0; j < k; j++) {   // 状态转移
            f[i][j] = max(f[i - 1][j], f[i - 1][(j + k - w % k) % k] + w);
        }
    }
    
    cout << f[n][0] << endl;
    
    return 0;
}

蓝桥真题:密码脱落

image-20250603202615967

注意,由于状态转移方程中会用到l+1r-1的状态,所以不能从大到小枚举或者从小到大枚举!

正确的做法是:

for 区间长度
	for 左端点
		右端点 = 左端点+区间长度-1

参考代码

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

using namespace std;

const int N = 1010;

string s;
int f[N][N];

int main()
{
    cin >> s;
    int n = s.size();
    
    for (int len = 1; len <= n; len++) {
        for (int l = 0;  l + len - 1 < n; l++) {
            int r = l + len - 1;
            if (len == 1) f[l][r] = 1;  // 初始化
            else {
                if (s[l] == s[r]) f[l][r] = f[l + 1][r - 1] + 2;
                f[l][r] = max(f[l][r], f[l][r - 1]);
                f[l][r] = max(f[l][r], f[l + 1][r]);
                
            }
        }
    }
    
    cout << n - f[0][n - 1] << endl;
    
    return 0;
}

蓝桥真题:生命之树

求一个连通块,使得这个连通块的所有(和谐)值的和最大。

参考代码


第十章 综合复习

补充

1. 简单博弈论

必胜态:存在一种操作方式,使得操作之后让对手变成必败态

必败态:不论怎么操作,对手都会变成必胜态

AcWing 5538. 回文游戏

首先,模拟可知

0 是必败态

1~9是必胜态

10是必胜态

猜想:

n如果末位是0,则必败;n如果末位不是0,则必胜

证明:

假设先手第一次取的数量是x。

记:

  • 当前$n$的末位不是$0$的状态为s1

  • 当前$n$的末位是$0$的状态为s2

分类讨论:

  • 如果初始时$n$的末位不是$0$

    此时状态为s1,让$x$等于$n$的个位数字。记剩余数量$n - x = k$,则$k$的末位为$0$,即状态变为s2,对手拿的数量$x$末位必定不是0(因为回文数的末位不可能是0),则对手拿完后必定会将状态重新变为s1,我们只需要重复前面操作直到剩余$k$为0,对手输掉比赛。

  • 如果初始时$n$的末位是$0$

    同上,此时状态为s2,先手只能将状态变为s1,无法将剩余数量$k$变为0,故此时先手必败。

参考代码

#include<iostream>
#include<string>

using namespace std;

string s;

int main()
{
    int t;
    cin >> t;
    while(t--) {
        cin >> s;
        if (s.back() == '0') printf("E\n");
        else printf("B\n");
    }
    
    return 0;
}

2. 二分查找

AcWing 5540. 最大限度地提高生产力

排序 + 二分查找

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

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 200010;	// 破环为链

int n, m;
int c[N], t[N];

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

	for (int i = 0; i < n; i++) scanf("%d", &c[i]);
	for (int i = 0; i < n; i++) scanf("%d", &t[i]), c[i] -= t[i];
	sort(c, c + n);

	while (q--) {
		int v, s;
		scanf("%d%d", &v, &s);

		int l = 0, r = n;
		while (l < r) {		// 找到第一个能进入的农场
			int mid = l + r >> 1;
			if (c[mid] > s) r = mid;
			else l = mid + 1;
		}
		
		if (n - r >= v) puts("YES");
		else puts("NO");
	}
	
	return 0;
}

LaTex常用语法

在 LaTeX 中表示异或符号(⊕)及其转义方式如下:


1. 直接表示异或符号

  • 标准符号:使用 \oplus 生成 ⊕
    示例:线性同余方程$ax\equiv b \pmod{n}$

  • 自定义样式(如加粗、颜色等):

    $\boldsymbol{\oplus}$  % 加粗
    $\textcolor{red}{\oplus}$  % 红色
    

2. 常见逻辑/位运算符号

符号 LaTeX 命令 示例输出
与(AND) \land\& $a \land b$
或(OR) \lor 或 ` `
非(NOT) \lnot $\lnot a$
异或(XOR) \oplus $a \oplus b$
同或(XNOR) \odot $a \odot b$

3. 转义符处理

LaTeX 中需转义的字符如下表,使用反斜杠 \ 进行转义:

字符 转义写法 用途说明
% \% 百分比符号
$ \$ 数学模式开关
& \& 表格对齐符
# \# 宏参数编号
_ \_ 下标
{ \{ 分组开始
} \} 分组结束
~ \textasciitilde\~ 非换行空格
^ \textasciicircum\^{} 上标
\ \textbackslash 反斜杠本身

示例

\$100 \& 50\%\_profit → $100 \& 50\%\_profit

4. 完整示例代码

\documentclass{article}
\usepackage{amsmath}  % 数学公式支持
\usepackage{xcolor}   % 颜色支持

\begin{document}

异或运算:$a \oplus b = c$  

转义字符示例:  
\$10 \& 20\%\_total → $10 \& 20\%\_total

自定义样式:  
$\textcolor{blue}{\oplus}$ → \textcolor{blue}{$ \oplus $}

\end{document}

注意事项

  1. 数学模式:逻辑符号需在 $...$$$...$$ 中使用。
  2. 包依赖amsmath 提供标准符号,xcolor 用于颜色设置。
  3. 兼容性\oplus\odot 在所有 LaTeX 发行版中默认支持。

以下是一些常见数学符号及其对应的 LaTeX 语法,按类别整理:


1. 希腊字母

符号 LaTeX 语法 符号 LaTeX 语法
$\alpha$ \alpha $\Alpha$ \Alpha (不常用)
$\beta$ \beta $\Beta$ \Beta (不常用)
$\gamma$ \gamma $\Gamma$ \Gamma
$\delta$ \delta $\Delta$ \Delta
$\epsilon$ \epsilon $\varepsilon$ \varepsilon
$\theta$ \theta $\Theta$ \Theta
$\pi$ \pi $\Pi$ \Pi
$\mu$ \mu $\Omega$ \Omega

2. 运算符

符号 LaTeX 语法 说明
$+$ + 加号
$-$ - 减号
$\times$ \times 乘号
$\div$ \div 除号
$\pm$ \pm 加减号
$\mp$ \mp 减加号
$\cdot$ \cdot 点乘(如 a \cdot b)
$\sum$ \sum 求和符号
$\prod$ \prod 求积符号
$\int$ \int 积分符号
$\lim$ \lim 极限符号
$\sqrt{x}$ \sqrt{x} 平方根
$\sqrt[n]{x}$ \sqrt[n]{x} n 次根

3. 关系符号

符号 LaTeX 语法 说明
$=$ = 等于
$\neq$ \neq 不等于
$\approx$ \approx 约等于
$\equiv$ \equiv 恒等于 (同余)
$\leq$ \leq 小于等于
$\geq$ \geq 大于等于
$\subset$ \subset 子集
$\subseteq$ \subseteq 包含于
$\in$ \in 属于
$\notin$ \notin 不属于

4. 箭头符号

符号 LaTeX 语法 说明
$\to$ \to 右箭头
$\rightarrow$ \rightarrow 右箭头
$\Rightarrow$ \Rightarrow 逻辑蕴含
$\leftrightarrow$ \leftrightarrow 双向箭头
$\uparrow$ \uparrow 上箭头

5. 括号与分隔符

符号 LaTeX 语法 说明
$($ ( 小括号
$[$ [\lbrack 中括号
${$ \{\lbrace 大括号
$\langle$ \langle 左尖括号
$\lfloor$ \lfloor 左地板括号
自适应括号 $\left( ... \right)$ \left( ... \right) 根据内容调整大小

6. 上下标与分式

符号 LaTeX 语法 示例
上标 x^2 $x^2$
下标 x_1 $x_1$
分式 \frac{a}{b} $\frac{a}{b}$
连分式 \cfrac{a}{b} 更紧凑的分式 $\cfrac{a}{b}$

7. 集合与逻辑符号

符号 LaTeX 语法 说明
$\forall$ \forall 对所有
$\exists$ \exists 存在
$\emptyset$ \emptyset 空集
$\mathbb{N}$ \mathbb{N} 自然数集
$\cap$ \cap 交集
$\cup$ \cup 并集

8. 矩阵与方程组

符号 LaTeX 语法 示例
矩阵 \begin{matrix} a & b \\ c & d \end{matrix} $\begin{matrix} a & b \ c & d \end{matrix}$
带括号矩阵 \begin{pmatrix} a & b \\ c & d \end{pmatrix} $\begin{pmatrix} a & b \ c & d \end{pmatrix}$
方程组 \begin{cases} x + y = 1 \\ x - y = 0 \end{cases} $\begin{cases} x + y = 1 \ x - y = 0 \end{cases}$

9. 微积分符号

符号 LaTeX 语法 说明
$\frac{dy}{dx}$ \frac{dy}{dx} 导数
$\partial$ \partial 偏导数符号
$\int_{a}^{b}$ \int_{a}^{b} 定积分
$\oint$ \oint 环路积分

10. 其他常用符号

符号 LaTeX 语法 说明
$\infty$ \infty 无穷大
$\nabla$ \nabla 梯度算子
$\angle$ \angle 角度
$\therefore$ \therefore 因此,所以
$\because$ \because 因为

示例代码

\documentclass{article}
\usepackage{amsmath} % 支持高级数学公式

\begin{document}
分式:$\frac{a}{b}$,根号:$\sqrt{x}$,求和:$\sum_{i=1}^{n} i^2$。

矩阵:
$$
\begin{pmatrix}
1 & 2 \\
3 & 4
\end{pmatrix}
$$

积分:$\int_{0}^{\infty} e^{-x} \, dx$
\end{document}

注意事项

  1. 数学模式:符号需在数学模式内使用($...$$$...$$)。
  2. 包依赖:部分符号(如矩阵)需要加载 amsmath 包。
  3. 空格处理:LaTeX 默认忽略数学模式中的空格,需用 \,\quad 手动调整间距。
posted @ 2025-11-26 10:05  挽安Wanan  阅读(2)  评论(0)    收藏  举报