2025蓝桥杯突击训练

2025蓝桥杯突击训练

个人的变量命名习惯

  • T test_case
  • mark 标记
  • n number
  • e edge
  • v vertex
  • v vector
  • v volume
  • v value
  • w weight
  • d depth
  • h head
  • u/v/w 输入边的起点终点和权重
  • vis visited
  • l left
  • r right
  • mid middle
  • pos position
  • p pointer
  • s/st start
  • ed end
  • m matrix
  • g graph
  • s set
  • q queue
  • c/cnt count
  • ans answer
  • ret return
  • res result
  • t/tmp temporary临时变量

数据分析

int的数据范围最高到1e9,超了记得换long long

第二步:构造 tm 结构体

c++
tm t = {0, 0, 0, d, m - 1, y - 1900};

这里用到了 <ctime> 库里定义的结构体 tm,这是 c/c++ 里专门用来表示日期的结构体。

结构如下:

c++
struct tm {
int tm_sec;   // 秒
int tm_min;   // 分
int tm_hour;  // 时
int tm_mday;  // 天
int tm_mon;   // 月(0~11)❗️注意不是 1~12
int tm_year;  // 年(从 1900 开始)❗️
};

所以:

  • t.tm_mday = d; // 天 = 11
  • t.tm_mon = m - 1; // 月 = 4 - 1 = 3(代表 4 月)
  • t.tm_year = y - 1900; // 年 = 2025 - 1900 = 125

第三步:日期 +1 天

c++
t.tm_mday += 1;

直接把天数加 1,可能会超出当前月的天数,比如加到 31 号或 29 号(2 月)等等。


第四步:让系统帮你“进位”

c++
mktime(&t);

mktime 会自动处理你加了一天后产生的 月进位 / 年进位 / 闰年处理

比如:

  • 2月28日 +1 → 3月1日(非闰年)
  • 2月28日 +1 → 2月29日(闰年)
  • 12月31日 +1 → 下一年 1月1日

算法1-3暴力枚举

P2241统计方形

参考的大佬的题解:https://www.luogu.com.cn/problem/solution/P2241

注意要开long long,因为最坏的情况是从1➕到5000 * 5000,超出了int

等差数列求和公式:

S=n*(n+1) / 2

P2089烤鸡

题目链接: https://www.luogu.com.cn/problem/P2089

一道比较简单的dfs,这里需要考虑的是最多有几种方案,因为题目说n最大5000,但是从题目意思可知,美味程度最大是30.数据量较小,如果非要说确定的话,3的10次方,最多开6w即可。

P1618三连击

P1036选数

该题的关键是选出数的组合不能重复,即位置不同也不行

如1 、2、3和2、1、3是同一种

因此要设置一个参数来控制选数的顺序,每次选的时候只从他后面选,即可。

P1088火星人

该题的意思就是从给定的一个全排列,顺序往下m个,然后输出他。那么在写的时候按全排列写即可,第一次直接定位到输入的全排列即可

P3799小Y拼木棒

P1044栈

写递归的要点

明白一个函数的作用并相信它能完成这个任务,千万不要跳进这个函数里面企图探究更多细节, 否则就会陷入无穷的细节无法自拔,人脑能压几个栈啊。

算法1-4递推与递归

P1464记忆化搜索

P1928 外星密码

P1255 数楼梯

P2437 蜜蜂路线

和数楼梯思路一样,只不过要上的阶数是n-m。

P1164 小A点菜

暴力dfs版本(果然TLE了)

动态规划正解

这是一道简单的动规题,定义f[i]j为用前i道菜用光j元钱的办法总数,其状态转移方程如下:

(1)if(j==第i道菜的价格)f[i]j=f[i-1]j+1;

(2)if(j>第i道菜的价格) f[i]j=f[i-1]j+f[i-1]j-第i道菜的价格;

(3)if(j<第i道菜的价格) f[i]j=f[i-1]j;

说的简单一些,这三个方程,每一个都是在吃与不吃之间抉择。若钱充足,办法总数就等于吃这道菜的办法数与不吃这道菜的办法数之和;若不充足,办法总数就只能承袭吃前i-1道菜的办法总数。依次递推,在最后,我们只要输出f[n]m的值即可。

P1990 覆盖墙壁

GN:铺满前 N 列墙,且第 N+1列有一个单元被覆盖的方案数,且不考虑第N+1列的格子是上还是下!!

F2=2,F2 = F1+F0+2*G0, F1 = 1, F0 = 1,所以G0 = 0,又因为G1 = F0 + G0,所以G1 = 1. F0 = 1即什么都不放也是一种方案。

FN表示铺满前N*2的面积的墙的方案数.

FN的转移方程就是:

FN=FN-1+FN-2+2*GN-2(别忘了前面讲过GN-2的情况有两种

而GN的转移方程就是:GN=FN-1+GN-1

初始化:F0=1,G0=0;F1=G1=1;

image-20250404091917380

但是,L形的瓷砖又怎么办呢?

(呵呵,刚开始想到这里的时候,我自己都蒙了。)

为了方便大家思考,我们先往简单的方向想。(以下是重点!!!


我们可以用一个数组GN来表示**铺满前(N+1)*2的面积的墙,但是第(N+1)列有一个瓷砖已经被铺过(注意,是已经被铺过!)**的方案数。

所以,L形瓷砖的问题就已经被“初步”解决了。

所以,下面这种情况的方案数就是GN-2(因为实际上第N列已经铺满了,所以这里要处理的是前N-1列墙,所以多减了1)(如下图所示):

image-20250404092007618

P3612 Secret cow code S

先看样例

cOW*−>cOW WcO−>*cOWWcO OcOWWc

我们把这三个字符串编号为1,2,3

现在我们要求第8位,假如我们已经知道在3串,能否逆推出在第2串中的位置呢?如果能,似乎问题就迎刃而解了,因为2逆推到1也是一个相同的子问题。

题目的古怪要求复制要先复制最后一个字符,再复制前缀,我们姑且先直接复制前缀.

cOW−>cOW cOW*−>*cOWcOW cOWcOW

现在求3串的8位置,3串长L,逆推回2串即为8−L/2位置

但我们复制的时候是先复制最后一个字符,所以要多减一为8−1−L/2

特别的,如果求的n刚好是先复制原串的最后一个位置,特殊处理

因为如果是原串的最后一个位置,假设原串长为L,则复制后的串为2L,若位置x - L / 2 - 1 == 0 即是原串的最后一个位置,则将他赋值为i

P1259 黑白棋子的移动

最左边的o*与空位交换 然后空位再和最右边连续**的最后**两个交换

但是注意当o*与空位交换之后,连续的白棋只剩三个的时候规律发生了变化,此时直接打表。

P1010幂次方

P1228 地毯填补问题

棋盘是如何划分的:

  1. 设当前棋盘的左上角坐标为 (a, b),边长为 l
  2. 该棋盘被划分成四个 l/2 × l/2 的小棋盘:
    • 左上角子棋盘范围: 横坐标: [a, a + l/2 - 1]纵坐标: [b, b + l/2 - 1]
    • 右上角子棋盘范围: 横坐标: [a, a + l/2 - 1]纵坐标: [b + l/2, b + l - 1]
    • 左下角子棋盘范围: 横坐标: [a + l/2, a + l - 1]纵坐标: [b, b + l/2 - 1]
    • 右下角子棋盘范围: 横坐标: [a + l/2, a + l - 1]纵坐标: [b + l/2, b + l - 1]

void dfs(ll x, ll y, ll a,ll b, ll l)//(x,y)是障碍点,(a,b)是当前棋盘的左上角坐标,l是棋盘边长

初看这个问题,似乎无从下手,于是我们可以先考虑最简单的情况,既n = 2

0 0 0 1 这时,无论公主在哪个格子我们都可以用一块毯子填满

继续考虑n = 4的情况

我们已经知道了解决2 * 2的格子中有一个障碍的情况如何解决,因此我们可以尝试构造这种情况

首先,显然可以将4 * 4的盘面划分成4个2 * 2的小盘面,其中一块已经存在一个障碍

而我们只需在正中间的2 * 2方格中放入一块地毯,就可以使所有小盘面都有一个障碍

于是,n = 4的情况就解决了

我们可以将n = 4时的解法可以推广到一般情况,既当n = 2 k时,我们均可以将问题划分为4个n = 2 k – 1的子问题,然后分治解决即可。

P1498 南蛮图腾

c++
for(int j=i;j>0;j--)a[j]^=a[j-1];//修改数组

动态生成分形图案的每一行状态,它本质上是在模拟杨辉三角(Pascal's Triangle)的生成过程,但只关心每个位置的奇偶性(用异或运算实现)。

为什么倒序更新?

假设我们有一个数组 a = [1, 1, 0, 1],想要生成下一行:

  • 正序更新(从左到右):会覆盖前面的值,导致后续计算错误。
  • 倒序更新(从右到左):先处理高位,保留低位未修改的值,确保计算的正确性。

例如,生成杨辉三角第3行(索引从0开始):

  • 原数组:[1, 2, 1](但这里只关心奇偶性,实际存储的是 [1, 0, 1]
  • 生成第4行时,需要从右向左更新,避免覆盖前一行数据。
c++
#include<iostream>
using namespace std;
int n,a[1030]={1};//初始化数组,第一个元素为1,其余为0

int main(){
    cin>>n;
    for(int i=0;i<1<<n;i++){//共2的n次方行
        for(int j=1;j<(1<<n)-i;j++)cout<<" ";//前导空格,1-2^n-1,1-2^n-2...
        
        for(int j=i;j>0;j--)a[j]^=a[j-1];//修改数组
        if(!(i%2))for(int j=0;j<=i;j++)cout<<(a[j]?"/\\":"  ");//奇数行,2个空格,1个0等于2个空格
        else for(int j=0;j<=i;j+=2)cout<<(a[j]?"/__\\":"    ");//偶数行,4个空格
        cout<<endl;
    }
    return 0;
}

算法1-5贪心

总结

贪心的题一般会进行排序,并且多用结构体。一般是最小最大问题

P1223 排队接水

P1803 凌乱的yyy / 线段覆盖

这道题贪心的思路是每次选择结束时间最早的,这样能为后面留下更多的时间参赛。

P1090合并果子

复杂度是 O(n^2)超时

sort复杂度是O(nlogn)

O(n)做法

🍔 先说说:什么是堆?

  • (Heap)是一种特殊的完全二叉树,在编程里常用来做优先级排序
  • 有两种堆:
    • 最大堆:顶端是最大的元素(默认的 priority_queue
    • 最小堆:顶端是最小的元素(我们需要的!)

🧰 c++ 默认的 priority_queue 是最大堆

🧲 想要最小堆怎么办?

我们用这个写法:

c++
priority_queue<int, vector<int>, greater<int>> q;

解释一下:

  • int:存放的类型
  • vector<int>:底层容器
  • greater<int>:比较函数,告诉它“小的优先”,也就是最小堆!

🪄 你可以记住这个最小堆模板:

c++
priority_queue<类型, vector<类型>, greater<类型>> 变量名;

📝 总结一下

操作意义举例
push(x)x 放进堆里pq.push(5);
pop()删除堆顶元素(最小值)pq.pop();
top()查看堆顶元素(最小值)cout << pq.top();

仅堆顶可读

贪心思路:每次选择最小的两堆进行合并

P3817 小A的糖果

如果相邻两个盒子糖果的数量大于 x,就吃右边盒子的,否则不进行任何操作。

为什么要吃右边盒子的糖:这是因为如果我们吃掉左边盒子里的糖,就只会减少这一轮相邻两个盒子糖果的数量;如果我们吃掉右边盒子里的糖,那么这次操作还可以减少下一轮相邻两个盒子糖果的数量,符合贪心的逻辑。

P1106 删数问题

sort函数的用法

默认是从小到大排序,如果要从大到小排序,则可写成如下格式:

c++
sort(a,a+len,greater<int>());

重点是原左右次序

🧠 我们先说 string 的 erase 用法

c++
string str = "abcdef";
str.erase(pos, len);  // 从 pos的索引位置开始,删除 len 个字符

✅ 示例 3:只给一个参数,删除从这个位置到末尾

c++
string str = "abcdef";
str.erase(3); // 删除从索引3开始(含)之后的所有字符
cout << str;  // 输出 abc

🧠 vector 的 erase 用法也很类似

这里只给一个参数,只能删除给定位置索引的元素,不会删后面的

c++
vector<int> v = {1, 2, 3, 4, 5};
v.erase(v.begin() + 2); // 删除索引为 2 的元素(也就是 3)

你也可以删除一个范围:(含头不含尾)

c++
v.erase(v.begin() + 1, v.begin() + 4); // 删除 2~4(含头不含尾),结果是 {1, 5}

贪心的思想是每次删除数字中的极大值

❓ 你想让一个数变小,怎么做?

从左到右,先比较高位!

  • 最高位大 → 整体大
  • 所以你想尽早删掉一个大数
  • 如果你删的是左边的“高位的大数”,整体数就更小

所以: 从左往右找到第一个比后面大的数,删掉它,最有“贡献”

解法二:

P1478 陶陶摘苹果

贪心的思路是先取花费力气少的,留下更多的力气去拿后面的。

P5019 铺设道路

贪心的核心思想:

  • 如果 a[i]a[i-1]:那么我们知道从 a[i-1]a[i] 之间的差就是需要额外填充的部分,因此我们加上 a[i] - a[i-1]
  • 如果 a[i]a[i-1]:这个时候,我们不需要额外填充,只要关注前一个坑即可,因为当前坑已经被前一个坑的操作填补掉了。

为什么贪心是对的:

  • 如果 a[i]a[i-1] 大,直接填充当前的差值 a[i] - a[i-1],这相当于我们处理一个新坑的深度。
  • 如果 a[i]a[i-1] 小,那么前一个坑已经处理过它并填充了这个部分,当前坑不需要额外的操作。

P1208Mixing Milk

P1094 纪念品分组

我们先将数据进行排序,然后维护两个变量 xy,让 x 指向开头,让 y 指向结尾。

一直循环,过程中会出现两种情况。

  1. 如果当前两个变量所指的两个数之和小于或等于 w,说明可行,就把它们两个分为一组,同时将 x 加 1,将 y 减 1,并将答案加 1,这是第一种情况。
  2. 如果当前两个变量所指的两个数之和大于 w,说明不可行,只将 y 减 1,同时答案加 1 即可,这是第二种情况。

重复以上过程,直到 x>y 时停止循环。

P4995 跳跳!

记得开long long,因为hi最大可能为1e4,平方完1e8,继续加可能爆int

便捷的写法:

P4447 分组

在 c++ 中,map 是一个非常常用的 关联容器,定义在 <map> 头文件中。它是 STL(标准模板库) 的一部分,提供了 键值对(key-value) 的数据结构

🌟 简要定义

c++
std::map<KeyType, ValueType>
  • KeyType:键的类型(必须支持 < 比较)
  • ValueType:值的类型

map自动按照 key 排序,通常是按升序排列(默认使用 < 运算符)。

🔧 常用操作

操作示例说明
插入m["key"] = value;m.insert({key, value});插入或修改元素
查找m.find(key)返回迭代器,指向元素;若不存在,返回 m.end()
删除m.erase(key);删除指定 key 的元素
判断是否存在m.count(key)返回 0 或 1(map 不允许重复键)
大小m.size()元素个数
清空m.clear()删除所有元素

✅ 正确写法回顾

c++
std::map<int, int> m;
auto i = m.begin();
// 第一种写法(解引用 + 点)
(*i).second--;
// 第二种写法(推荐,简洁)
i->second--;

P1080 国王游戏

image-20250408144329517

**ans1是最高位,**高位在前,低位在后

**p1是最高位,**高位在前,低位在后

sum1 是最低位,低位在前,高位在后

✅ 对比总结一下

数组用途低位高位
sum当前正在参与计算的数(参与乘法和除法sum[1]sum[m]
ans每次除法的结果ans[ls]ans[1]
p最终记录的最大结果p[lp]p[1]

所以这份代码内部其实用了两种顺序:

  • 计算时(如乘法)用“低位在前”顺序(便于进位)
  • 结果保存/比较/输出时用“高位在前”顺序(符合人类习惯)

算法1-7搜索

P1135 奇怪的电梯

1. memset(dist, 0x3f, sizeof(dist)) 实际干了啥?

  • memset 会把内存中每个 字节(byte)都设成 0x3f(也就是十进制的 63)。
  • 而一个 int 在 c++ 中通常是 4 个字节(32 位)

所以,每个 int 被填成:

c++
0x3f3f3f3f

P1219八皇后 checker challenge

P1443 马的遍历

宽度优先搜索(BFS)特别适合用来找“无权图”的最短路!

🧠 为什么?

因为 BFS 是一层一层扩展的,它保证了:

第一次到达某个点的时候,所走的步数就是最短的。

dfs结果超时

P2895 Meteor Shower S

✅ 那为什么 BFS 要判断“有没有走过”呢?

因为——

BFS 是一层一层地找最短路的,如果一个点你已经访问过,说明你之前已经用更短的时间走到它了!现在再来一次,就是浪费时间,还可能是“更慢的路径”。

算法2-1前缀和、差分与离散化

P8218 求区间和

动态规划1

P2196 挖地雷

暴力DFS写法

Ac贪心:

memcpy 的正确用法是:

c++
void *memcpy(void *dest, const void *src, size_t n);

也就是:

c++
memcpy(目标地址, 源地址, 拷贝的字节数);
Git自动化部署
Java苍穹外卖

评论区

评论加载中...