蓝桥杯准备
序
刷题量:200
心态:刚开始听不懂写不出题很正常,一定要保持号良好的心态,当听到一定的时间之后,自然而然就可以自己写出题来.
学习时间:一旦准备开始学习,学习的时间一定要连贯,不要断断续续,可以学习的时间少,但是不能长时间间断。一鼓作气,再而衰,三而竭。
笔记:有能力尽量要撰写自我的学习笔记,即使开始很乱,但是一定要坚持写作,这对将来的你一定会有很多的帮助。
复习:一定要定时复习,不要相信自己可以一遍就可以刷过,多来几次没有任何坏处。
如何做题
(数据范围很重要,刚开始先尽量看一下)
题型讲解
递归
核心思想:自己调用自己
引:斐波那契数列:
//第一天为1,第二天为2,以后每一天是前两天之和。
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int f(int n){
if (n==1) return 1;
if (n==2) return 2;
return f(n-1)+f(n-2);
}
int main(){
int n;
cin >>n;
cout <<f(n)<<endl;
}
所有的递归都可以转化成递归搜索树(不会的时候尽量画一画)
小tip:背一下
2的1到10次方
递归实现枚举
从1-n这n个整数 随机选取任意多个,输出所有可能的选择方案。
输入格式
输入一个整数n。输出格式
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器 (SPJ),各行 (不同方案)之间的顺序任意。数据范围
1 < n< 15输入样例:
3输出样例
3
2
2 3
1
1 3
1 2
1 2 3进程已结束,退出代码0
分析:
- 每个数都有选和不选两种情况=共有2的n次方方案
- 每个方案输出长度为n
- 所有复杂度 2的n次方*n
递归(dfs)
最重要的是顺序,不重不漏的所有方案找出来。
从1~n依次考虑每个数选或者不选。
代码实现
输出结果
//常用函数库
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
//定义数据范围
const int N=15;
int n;
int st[N]; //状态,记录每个位置的状态;0表示还没有考虑,1表示选他,2表示不选他
void dfs(int u){
//设置边界,
if(u>n) {
//为了方便,我们让下表从1开始
for (int i = 1; i <=n ; ++i)
if(st[i]==1)
cout << i << " ";
cout <<endl;
return;
}
st[u]=2;
dfs(u+1);//第一个分支;不选
st[u]=0;//恢复现场
st[u]=1;
dfs(u+1);//第二个分支;选
st[u]=0; //恢复现场
}
int main(){
cin >>n;
dfs(1);
}
输出记录
//常用函数库
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 15;
int n;
int st[N];
vector<vector<int>> ways; // 记录组合的二维向量
void dfs(int u) {
// 达到边界时记录组合
if (u > n) {
vector<int> way;
for (int i = 1; i <= n; ++i) {
if (st[i] == 1)
way.push_back(i);
}
ways.push_back(way);
return;
}
st[u] = 2;
dfs(u + 1); // 第一个分支;不选
st[u] = 0; // 恢复现场
st[u] = 1;
dfs(u + 1); // 第二个分支;选
st[u] = 0; // 恢复现场
}
int main() {
cin >> n;
dfs(1);
// 输出所有组合
for (int i = 0; i < ways.size(); ++i) {
for (int j = 0; j < ways[i].size(); ++j) {
cout << ways[i][j] << " ";
}
cout << endl;
}
return 0;
}
递归实现排序
把1~n这个整数排成一行后随机打乱顺序,输出所有可能的次序。
输入格式
一个整数n.
输出格式
按照从小到大的顺序输出所有方案,每行1个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,享典序较小的排在前面。数据范围
1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
知识点补充:
字典序
全排列
分析:
递归首要的是顺序:
依次枚举每个数放在那个位置
依次枚举每个位置放那个数(想不明白画递归搜索树)
代码实现
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=10;
int n;
int state[N]; //0表示还没有放数字,1~n表示放了那个数字
bool used[N];//true 表示用过,false表示从未用过
void dfs(int u){
if(u>n){
for(int i=1;i<=n;i++) cout << state[i]<<" ";
cout <<endl;
return;
}
//依次枚举每个分支,当前位置可以填那些数字
for(int i=1;i<=n;i++)
{
if(!used[i])
{
state[u]=i;
used[i]= true;
dfs(u+1);
//回复现场
state[u]=0;
used[i]=false;
}
}
}
int main(){
cin >> n;
dfs(1);
}
递归实现组合型枚举
递归实现组合型枚举
题目
从1 这n个整数中随机选出m个,输出所有可能的选择方案输入格式
两个整数n,m 在同一行用空格隔开。输出格式按照从小到大的顺序输出所有方案,每行1个。
首先,同一行内的数升序排列,相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一一比较,字典序较小的排在前面 (例如1357排在1368前面)。数据范围
n>0
0< =n<=m;
n+(n一m)<=25
输入样例:5 3
输出样例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
分析:为了保证数字从小到大且不重复,便可人为的添加限制使其从小到大排序,所以只需要添加后一个的数大于前一个数就可以
利用人为排序之后的递归树(1~5)选3个
解题思路顺序:
搜索问题转化为搜索树
树转换成代码(思考dfs()里面的参数)
- 三个位置 way[N]
- 当前枚举那个位置 u
- start当前最小可以从哪个枚举开始
代码实现(基础)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N =30;
int n,m;//输入的数据
int way[N]; //所选数字的存储结构
//所需要传参的三个参数,因为所选数组结构已经定义为全局变量,所有不需要传参了
void dfs(int u,int start){
if(u==m+1){
for(int i=1;i<=m;i++) cout <<way[i];
cout <<endl;
return;
}
for(int i=start;i<=n;i++){
way[u]=i;
dfs(u+1,i+1);
way[u]=0;//恢复现场
}
}
int main(){
cin >> n>>m;
dfs(1,1); //当前枚举的位置,从小从1开始
}
代码实现(减枝)
如果发现分支没有解,体现退出,如上面数的四五分支。
假设当前 正在选第u个数,说明已经选了有u-1个数,后面我们就可以选start到n(start是移动的,这个时候已经不在开始了),把后面的所有数字(n-start+1)+u-1<m,就一定不成立,所有直接排除
进一步化简u+n-start<m 便可以提前退出
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N =30;
int n,m;//输入的数据
int way[N]; //所选数字的存储结构
//所需要传参的三个参数,因为所选数组结构已经定义为全局变量,所有不需要传参了
void dfs(int u,int start){
if(u+m-start<m) return;
if(u==m+1){
for(int i=1;i<=m;i++) cout <<way[i];
cout <<endl;
return;
}
for(int i=start;i<=n;i++){
way[u]=i;
dfs(u+1,i+1);
way[u]=0;//恢复现场
}
}
int main(){
cin >> n>>m;
dfs(1,1); //当前枚举的位置,从小从1开始
}
带分数
题目
100可以表示为带分数的形式:100=3+69258/714还可以表示为:100=82+3546/197注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。
类似这样的带分数,100有11种表示法。输入格式
一个正整数。
输出格式
输出输入数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。数据范围
1≤N<106输入样例:
100输出样例:
11
tip:考试的时候一定挑一个最好写的,而不是最优解。所有学不会暴力解题很重要。
分析:暴力解题
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 30;
int ct[N]; // 存储当前排列的数组
bool used[N]; // 标记数字是否已被使用
int n; // 输入的整数
int ans; // 记录符合条件的排列数量
// 将数组中的一段数字转换成整数
int res(int l, int r) {
int result = 0;
for (int i = l; i <= r; i++) {
result = result * 10 + ct[i];
}
return result;
}
// 深度优先搜索函数
void dfs(int u) {
if (u == 10) {
// 当排列长度为10时,将数字分成三段并检查是否满足条件
for (int i = 1; i <= 7; i++) {
for (int j = i + 1; j <= 9; j++) {
int a = res(0, i);
int b = res(i + 1, j);
int c = res(j + 1, 9);
if (c * n == c * a + b) {
ans++;
}
}
}
return;
}
for (int i = 1; i <= 9; i++) {
// 遍历1到9的数字,进行全排列
if (used[i] == false) {
ct[u] = i;
used[i] = true;
dfs(u + 1);
ct[u] = 0;
used[i] = false;
}
}
}
int main() {
// 输入整数n
cin >> n;
// 调用深度优先搜索函数
dfs(1);
// 输出结果
printf("%d", ans);
return 0;
}
优化之后(不要求会,看看就可以,反正我看不明白)
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=20;
bool st[N],backup[N];
int ans=0,n;
bool check(int a,int c)
{
int b=c*n-a*c; //算出b了
if(!a||!b||!c) return false; //a,b,c一个都不能为0
memcpy(backup,st,sizeof(st)); //st数组中反映了a和c中1-9数字的使用情况
while(b) //备份给backup,不能作判断的时候把st毁了,后面递归还需要用
{
int x=b%10; //把b的每一位掏出来
b/=10;
if(!x||backup[x]) return false; //如果b里有0或者有和a、c重复的情况,不符合条件
backup[x]=true; //把b里面用的数字也标记为用过了
}
for(int i=1;i<=9;i++) //判断9个数字是否都用过了
if(!backup[i])
return false;
return true;
}
void dfs_c(int u,int a,int c) //第一个参数:当前已经用了几个数字了
{ //第二个参数:a的值;第三个参数:c的值
if(check(a,c)) ans++; //固定一个a和c,去算出b,判断是否符合条件
for(int i=1;i<=9;i++)
{
if(!st[i])
{
st[i]=true;
dfs_c(u+1,a,c*10+i);
st[i]=false;
}
}
}
void dfs_a(int u,int a){ //第一个参数:已经用了多少数字,第二个参数:当前a的值
if(a>=n||u>=9) return; //如果a和n一样大,意味着b和c为0,不符合条件;如果已经用了正好或者超过9个数字了,也意味着b和c为0,不符合条件
if(a) dfs_c(u,a,0); //假设a符合条件(a自己也不能为0)
//去递归dfs c
for(int i=1;i<=9;i++)
{
if(!st[i])
{
st[i]=true;
dfs_a(u+1,a*10+i);
st[i]=false; //恢复现场
}
}
}
int main(){
scanf("%d",&n);
dfs_a(0,0);
printf("%d",ans);
return 0;
}
递推
引:斐波那契数列:
先求子问题,然后推到到总问题。小到大
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
int main(){
int n;
cin >>n;
int f[46];
f[1]=0,f[2]=1;
for(int i=3;i<=n;i++) f[i]=f[i-1]+f[i-2];
for(int i=1;i<=n;i++) cout << f[i] << endl;
}
动态滚动
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
int main(){
int n;
cin >>n;
int a=0,b=1;
for(int i=1;i<=n;i++){
cout << a << " ";
int fn=a+b;
a=b,b=fn;
}
}
费解的开关
费解的开关
题目链接
你玩过“拉灯”游戏吗?25盏灯排成一个5x5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字“1”表示一盏开着的灯,用数字“0”表示关着的灯。下面这种状态10111
01101
10111
10000
11011
在改变了最左上角的灯的状态后将变成:01111
11101
10111
10000
11011
再改变它正中间的灯后状态将变成:01111
11001
11001
10100
11011
给定一些游戏的初始状态,编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。输入格式
第一行输入正整数n,代表数据中共有n个待解决的游戏初始状态。以下若干行数据分为n组,每组数据有5行,每行5个字符。每组数据描述了一个游戏的初始状态。各组数据间用一个空行分隔。
输出格式
一共输出n行数据,每行有一个小于等于6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。对于某一个游戏初始状态,若6步以内无法使所有灯变亮,则输出“-1”。
数据范围
0<n≤500输入样例:
3
00111
01011
10001
11010
1110011101
11101
11110
11111
1111101111
11111
11111
11111
11111输出样例:
3
2
-1
分析:
- 顺序可以任意
- 每个格子最多按一次
- 没有行开关的操作,完全被前一行的灯的亮灭状态决定。
宏观思考:
所以第一行的状态就直接决定了所有。即第一行确定之后,只有按第二行使得第一行全量,第三行使得第二行全量。直到n-1行。然后看最后一行,最后一行无法无法改变,若全亮则可以,不全亮则失败。
细节补充:
如何枚举第一行
因为5*5的格子且都用零一表示,所以_ _ _ _ _转化为十进制为0~25-1表示。那么如何判断这个位置的数字那?
现将十位转化成二进制,然后再把想判断的为数使用>>到各位然后
turn函数的书写
注:建立x,y与平时不一样,
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 6; // N 是数组大小,包括空字符 '\0'。
char g[N][N], backup[N][N]; // 二维数组用于表示初始状态和备份状态。
int dx[5] = {-1, 0, 1, 0, 0}, dy[5] = {0, 1, 0, -1, 0}; // 用于表示相邻单元的偏移。
// 函数用于翻转指定位置及其相邻位置的状态。
void turn(int x, int 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;
cin >> T; // 读取测试用例的数量。
while (T--) {
for (int i = 0; i < 5; i++) cin >> g[i]; // 读取初始状态。
int res = 10; // 初始化结果,设置为一个较大的值。
for (int op = 0; op < 32; op++) {
// 备份当前状态。
memcpy(backup, g, sizeof g);
int step = 0; // 记录当前操作的步数。
// 根据二进制掩码op执行翻转操作。
for (int i = 0; i < 5; i++) {
if (op >> i & 1) {
step++;
turn(0, i);
}
}
// 处理第1到第4行的状态翻转。
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 5; ++j) {
if (g[i][j] == '0') {
step++;
turn(i + 1, j);
}
}
}
// 检查第5行是否有黑色方块。
bool dark = false;
for (int i = 0; i < 5; i++) {
if (g[4][i] == '0') {
dark = true;
break;
}
}
// 如果第5行没有黑色方块,则更新结果。
if (!dark) res = min(res, step);
// 恢复到备份状态,进行下一次操作的尝试。
memcpy(g, backup, sizeof g);
}
// 如果结果大于6,则表示无法达到目标状态。
if (res > 6) res = -1;
// 输出结果。
cout << res << endl;
}
return 0;
}
翻硬币
问题描述
小明正在玩一个“翻硬币”的游戏。桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。
比如,可能情形是:oo*oooo
如果同时翻转左边的两个硬币,则变为:oooo***oooo
现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?
我们约定:把翻动相邻的两个硬币叫做一步操作,那么要求:
输入格式
两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000输出格式
一个整数,表示最小操作步数。样例输入1
xxxxxxxxxx
oxxxxoxxxx
样例输出1
5
样例输入2
xoxxoxxxoxxxxoxxxoxxoxxx
样例输出2
1
分析:转化为开关问题
(加粗的——为按下)
是个硬币为正面x,开关看做——,以为一个开关控制两个,所以仅仅需要九个开关
原本: x—— x —— x—— x—— x—— x—— x——x ——x ——x
结果:o—— x —— x—— x—— x—— 0—— x——x ——x ——x
要让原本变成结果,所以一定要按第一个开关
按一下:0**——** 0—— x—— x—— x—— x—— x——x ——x ——x
要和结果一样,所以第二个也一定要按一下
按两下:0**——** x**——** 0—— x—— x—— x—— x——x ——x ——x
等等等,直到最后:
结果:0**——** x**——** x**——** x**——** x**——** 0—— x——x ——x ——x