蓝桥杯题解day3(搜索DFS+BFS+二分+递归+博弈)

什么是dfs

深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First
Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在
leetcode,高频面试题中。

介绍

深度优先遍历主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。

深度优先搜索的步骤分为
1.递归下去
2.回溯上来。
顾名思义,深度优先,则是以深度为准则,先一条路走到底,直到达到目标。这里称之为递归下去。 否则既没有达到目标又无路可走了,那么则退回到上一步的状态,走其他路。这便是回溯上来。

例题(蓝桥真题)

小朋友崇拜圈

题目链接:https://www.lanqiao.cn/problems/182/learning/

在这里插入图片描述

在这里插入图片描述

解题
在这里插入代码片#include<bits/stdc++.h>
using namespace std;
int ans=0;
int start;	// 起点 
int a[100010]; //存值 i是本身下表  a【i】 是下一个下表 
int b[100010]={
    
    0}; //b是所到达的位置  如果到达重复的 就结束 
void dfs(int k,int sum) //当前为k sum和为多少个 
{
    
    
	if(b[k]==1)
		{
    
    
			if(a[k]==a[start])
				{
    
    
					if(sum>=ans)
						ans=sum;
					 //return 一开始写在这  然后 想了半天 为啥错  傻子!!! 
				}
		return ; //return的位置一定要写对  
		}
		
		//表示当前这个点走过了 
		b[k]=1;
		dfs(a[k],sum+1);
		b[k]=0;  //一定要记得清空 不然 会影响下次的结果 
	
	
}

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

	for(int i=1;i<=n;i++)
		cin>>a[i]; //i是自己的位置   a[i]是指向的位置
	
	for(int i=1;i<=n;i++)
		{
    
    
			start=i; // 起点 
			dfs(i,0);
		 } 
	 cout<<ans<<endl;
		
}

什么是bfs

介绍

广度优先搜索较之深度优先搜索之不同在于,深度优先搜索旨在不管有多少条岔路,先一条路走到底,不成功就返回上一个路口然后就选择下一条岔路,而广度优先搜索旨在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路,并重复这样的操作。

类似 层序遍历!!!

例题(蓝桥真题)

长草

题目链接: https://www.lanqiao.cn/problems/149/learning/

题目描述

小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。

小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。

这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,

这四小块空地都将变为有草的小块。请告诉小明,k 个月后空地上哪些地方有草。

输入描述

输入的第一行包含两个整数 n, m。

接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。

接下来包含一个整数 kk。 其中 2≤n,m≤1000,1≤k≤1000。

输出描述

输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。

输入

4 5
.g...
.....
..g..
.....
2

输出

gggg.
gggg.
ggggg
.ggg.
解题:
#include<bits/stdc++.h> 
using namespace std;
int n,m;
char a[1010][1010];  //初始值 
char b[1010][1010];
void bfs() 
//为什么要使用一个b呢  不然 你地址传递 就是动态修改  你需要的是利用这一次修改下一次的值 而不是动态修改
{
    
    
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
		b[i][j]=a[i][j];
	
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
	if(b[i][j]=='g'){
    
     //当前为 地  那么就长!!!  
	 {
    
    
	 	if(i>=1) //zuo
		  a[i-1][j]='g';
		if(j>=1) //上
		 	a[i][j-1]='g';
		if(i<n-1)
			a[i+1][j]='g' ;
		if(j<m-1)
			a[i][j+1]='g' ;		
	 }
	}
}
int main(){
    
    


	cin>>n>>m;
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			cin>>a[i][j];
		int k;
		cin>>k;
	for(int i=0;i<k;i++)
		bfs();
	for(int i=0;i<n;i++)
	{
    
    
		for(int j=0;j<m;j++)
			cout<<a[i][j];
			cout<<endl;
		}	
			
	
} 

什么是二分查找

介绍

二分査找就是折半查找,其基本思想是:首先选取表中间位置的记录,将其关键字与给定关键字 key 进行比较,若相等,则査找成功;若 key 值比该关键字值大,则要找的元素一定在右子表中,则继续对右子表进行折半查找:若 key 值比该关键宇值小,则要找的元素一定在左子表中,继续对左子表进行折半査找。如此递推,直到査找成功或査找失败(或査找范围为 0)。

例题(蓝桥真题)

分巧克力

题目链接:https://www.lanqiao.cn/problems/99/learning/

题目描述

儿童节那天有 K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N 块巧克力,其中第 i块是 Hi×Wi 的方格组成的长方形。为了公平起见,

小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:

1.形状是正方形,边长是整数;

2.大小相同;

例如一块 6x5 的巧克力可以切出 6 块 2x2 的巧克力或者 2 块 3x3 的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入描述

在这里插入图片描述

输出描述

输出切出的正方形巧克力最大可能的边长。

输入输出样例
输入

2 10
6 5
5 6

输出

2
解题:

可以将原问题理解为:在1~100000之间寻找一个maxX,使得将巧克力按照边长maxX进行切分,切分成的份数要大于等于K,而如果按照maxX+1进行切割,将不再能够切出K块。如果从1-100000逐个查找,那么肯定超时,所以采用二分查找。

代码解析:

#include<bits/stdc++.h>
using namespace std;
int a[110000],b[110000]; //长宽  
int n,k;
//由于一块大小为H*W(长为H,宽为W)的巧克力,
//可以切割成的边长为mid的正方形巧克力的块数为:(H/mid)*(W/mid)
bool check(int t){
    
    
	int sum=0;
	for(int i=0;i<n;i++)
	{
    
    
		sum+=(a[i]/t)*(b[i]/t);
	}
	if(sum>=k) return true;
	
	return false;
		
}
int main(){
    
    
	cin>>n>>k;
	for(int i=0;i<n;i++)
	cin>>a[i]>>b[i];
	
	int left=0;
	int right=100010; //  二分大小
	

	
	while(left<=right){
    
    
		int mid=(left+right)/2;
		if(check(mid))
			left=mid+1;
		else right=mid-1;
	}
	
	cout<<left-1<<endl;
	
	
} 

扫地机器人

题目链接: https://www.lanqiao.cn/problems/199/learning/

在这里插入图片描述

输出

一个整数代表答案。

样例输入

10 3
5
2
10

样例输出

6
解题:

分析:

挺明显的一个二分题目。我们二分每个机器人的扫地范围,根据数学知识我们可以知道,当每个机器人清扫的面积相差不大时,耗时最少。假设二分的扫地范围是x,对于每一个扫地机器人,我们尽可能让它扫地的右边界大一些,也就是扫过的格子,没有必要绝对不扫。最后看扫地的右边界是否大于等于格子的边界,如果是的话,就说明符合条件,否则就不符合条件。

ps:花费时间 = 2 × (扫地范围 - 1) 可以画图试一下!!!

代码解析:

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010;

int n, k;
int a[N];

bool check(int x)
{
    
    
    int sum = 0;                                  // sum 表示机器人已经扫到的区域右边界
    for (int i = 0; i < k; i ++)
    {
    
    
        if(a[i] - x > sum) 
			return false;          // 不能无缝衔接扫地区域,则一定失败
        else
        {
    
    
            if(a[i] <= sum) 
				sum= a[i] + x - 1;     // 在区域内,则从本区域开始,能扫到的最远区域
            else
				  sum += x;                       // 在区域外,则直接从边界累加
        }
    }
    
    return sum >= n;                              // 判断是否能扫完整个区域
}

int main()
{
    
    
    cin >> n >> k;
    for (int i = 0; i < k; i ++) cin >> a[i];
    
    sort(a, a + k);
    
    int l = 0, r = n;
    while(l < r)            
    {
    
    
        int mid = l + r >> 1;                   // 二分机器人的扫地范围
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    
    cout << 2 * l - 2 << endl;                  // 花费时间 = 2 × (扫地范围 - 1)
    return 0;
}


递归

介绍

借用百度百科的话:
程序调用自身的编程技巧称为递归( recursion)。

递归是一种解决问题的方法,其主要思想在于:

将问题分为规模更小的相同问题 持续分解,直到问题规模小到可以用非常简单直接的方式来解决

递归问题的分解方式非常独特,其算法方面的明显特征就是:在算法流程中调用自身。递归为我们提供了一种对复杂问题的优雅解决方案,精妙的递归算法常会出奇简单,令人赞叹。

例如:斐波拉契数列,阶乘,汉诺塔。

好了 废话不多说
接下来 我们来 说一说 蓝桥真题

模板

#include<stdio.h>
 // 这是一个阶乘的模板 其实都是差不多的 
int factor(int n)
{
    
    
	if (n == 1)
	{
    
    
		return 1;
	}
	return n * factor(n - 1);
}
 
int main()
{
    
    
	int n = 5;
 
	int ret = factor(n);
 
	printf("%d\n", ret);
 
	return 0;
}
写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。 :

如何做递归题

递归第一个步骤:明确函数要做什么 找子问题
递归第二个步骤:明确递归的结束(退出递归)条件 找出口
递归的第三个步骤:找到函数的等价关系式 如何递归

例题(蓝桥真题)

金额查错

题目链接:https://www.lanqiao.cn/problems/291/learning/
题目描述

某财务部门结账时发现总金额不对头。很可能是从明细上漏掉了某 1 笔或几笔。如果已知明细账目清单,能通过编程找到漏掉的是哪 1 笔或几笔吗?

如果有多种可能,则输出所有可能的情况。

输入描述

用户输入的第一行是:有错的总金额。

接下来是一个整数 n,表示下面将要输入的明细账目的条数。

再接下来是 n 行整数,分别表示每笔账目的金额。

为了方便,不妨假设所有的金额都是整数;每笔金额不超过 1000,金额的明细条数不超过 100。

输出描述

所有可能漏掉的金额组合。每个情况 1 行。金额按照从小到大排列,中间用空格分开。

输入输出样例

输入

6
5
3
2
4
3
1

输出

1 2 4
1 3 3
3 4
解题:

思路 就是 除去那些金额组合情况 然后剩下的金额加起来刚好等于一开始给的数字(乌鱼子 一开始 读了半天题目 没有读明白 真是个小天才(大傻逼))
代码:

#include<bits/stdc++.h> 
using namespace std;
int m;
// 其实 我不清楚 为什么这个题 划分到递归  感觉就是一个dfs
void dfs(int a[],int x,int err,int b[],int sum) 
//a 是数组,x是当前位置 err是错误的钱,b是使用了那些钱  sum 是已经统计的钱 
{
    
    
	if(sum>err)	
	return ;
	if(sum==err) //当前值  刚好成立 
	{
    
    
		 for (int i=1; i < m; i++) // 去重 
 {
    
    
 if (a[i] == a[i-1] && b[i-1] && !b[i]) return;
 }
			for(int i=0;i<m;i++)
		{
    
    
			if(b[i]==0)
			cout<<a[i]<<" ";
		} 
		cout<<endl;
		return ;
	}
	if(x>=m)
	return ;
	//如果 我不要当前值
	b[x]=0; 
	dfs(a,x+1,err,b,sum);
	
	//需要当前值
	b[x]=1;
	dfs(a,x+1,err,b,sum+a[x]);
	
	b[x]=0; //回溯 
}
int main(){
    
    
	int n;
	cin>>n;
	cin>>m;
	int a[m],b[m]={
    
    0};
	for(int i=0;i<m;i++)
		cin>>a[i];
	sort(a,a+m);
		
	dfs(a,0,n,b,0);
}
// 3 4 情况 输出了俩次  解决方法 
// 去重  因为3 3 出现俩次 那么 必定只选了一个 
//  for (int i=1; i < n; i++) // 去重 
// {
    
    
// if (a[i] == a[i-1] && b[i-1] && !b[i]) return;
// }

母牛的故事

题目链接: https://www.dotcpp.com/oj/problem1004.html

题目描述

有一头母牛,它每年年初生一头小母牛。每头小母牛从第四个年头开始,每年年初也生一头小母牛。请编程实现在第n年的时候,共有多少头母牛?

输入

输入数据由多个测试实例组成,每个测试实例占一行,包括一个整数n(0<n<55),n的含义如题目中描述。
n=0表示输入数据的结束,不做处理。

输出

对于每个测试实例,输出在第n年的时候母牛的数量。
每个输出占一行。

样例输入

2
4
5
0

样例输出

2
4
6
解题:

第n年的母牛的来源分别来自于前一年剩下的和往前推3年的母牛(能生的母牛,最早的也算在里面)

第五年:
6 = 前一年剩下的4头牛+(第2年新产的母牛刚具有生育能力生下的1头+最早的母牛1头)

    解题公式:f(n) = f(n-1) + f(n-3)
#include<bits/stdc++.h> 
using namespace std;
	int a[56]={
    
    0,1,2,3,4}; //为什么要用数组  因为 n<55   递归量不大 同时防止时间超限
int init(){
    
    
	for(int i=5;i<56;i++)
		a[i]=a[i-1]+a[i-3];
}
int main(){
    
    
	init();
	int n;
	cin>>n;
	while(n!=0){
    
    
	cout<<a[n]<<endl;
		cin>>n;
	}
}

博弈

介绍

博弈的策略:参与者在行动之前所准备好的一套完整的行动方案,就是想好下完这步棋,对方会如何下.

特点

1.博弈模型为两人轮流决策的非合作博弈。即两人轮流进行决策,并且两人都使用最优策略来获取胜利

2.博弈是有限的。即无论两人怎样决策,都会在有限步后决出胜负

3.公平博弈。即两人进行决策所遵循的规则相同

常见的博弈算法

1.巴什博弈

1、问题模型:有一个堆物品,物品数量为n个,两个人轮流从这堆物品中取物品,规定每次至少取一个,最多取m个,最后取光者得胜。

2、解决思路:当n=m+1时,由于一次最多只能取m个,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜,所以当一方面对的局势是n%(m+1)=0时,其面临的是必败的局势。所以当n=(m+1)*r+s,(r为任意自然数,s≤m)时,如果先取者要拿走s个物品,如果后取者拿走x(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。

结论:如果条件是最后取光者得胜,那么当先手面临的局势是n%(m+1)==0,先手必败
#include<stdio.h>
int main()
{
    
    
    int T;
    scanf("%d",&T);
    while(T--)
    {
    
    
       int n,m;
       scanf("%d%d",&n,&m);
       if(n%(m+1)==0)
           printf("second win\n");
       else
           printf("first win\n");
    }
    return 0;
}

3、变形:条件不变,改为最后取光的人输。

结论:如果条件是最后取光者失败,那么当先手面临的局势是(n-1)%(m+1)==0时,先手必败。

2.威佐夫博弈

1、问题模型:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

2、解决思路: 设(ai,bi) (ai ≤bi ,i=0,1,2,…,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。任给一个局势(a,b),如下公式判断它是不是奇异局势:ak=[k(1+√5)/2],bk=ak+k (k=0,1,2,…,n 方括号表示取整函数)。

3、满足上公式的局势性质:

(1)任何自然数都包含在一个且仅有一个奇异局势中。

由于ak是未在前面出现过的最小自然数,所以有ak>ak-1,而bk=ak+k>ak-1+k-1=bk-1>ak-1,所以性质成立。

(2)任意操作都可将奇异局势变为非奇异局势。

若只改变奇异局势(ak,bk)的某一个分量,那么另一个分量不可能在其他奇异局势中,所以必然是非奇异局势。如果使(ak,bk)的两个分量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势

(3)采用适当的方法,可以将非奇异局势变为奇异局势。

假设面对的局势是(a,b),若 b = a,则同时从两堆中取走 a 个物体,就变为了奇异局势(0,0);如果a = ak ,b >bk,那么,取走b – bk个物体,即变为奇异局势;如果 a = ak,b < bk ,则同时从两堆中拿走 ak – ab – ak个物体,变为奇异局势( ab – ak , ab – ak+ b – ak);如果a > ak,b= ak + k,则从第一堆中拿走多余的数量a – ak 即可;如果a < ak ,b= ak + k,分两种情况,第一种,a=aj (j < k),从第二堆里面拿走 b – bj 即可; 第二种,a=bj (j<k),从第二堆里面拿走 b – aj 即可。

4、结论:两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main()
{
    
    
    int a,b;
    while(~scanf("%d%d",&a,&b))
    {
    
    
        if(a>b)
            swap(a,b);
        int ans=floor((b-a)*(sqrt(5)+1)/2); //判断是否是奇异局势
        if(a==ans)
            printf("second win\n");
        else
            printf("first win\n");
    }
    return 0;
}

另外还有 斐波那契博弈 尼姆博弈 等等 不在一一介绍了

例题(蓝桥真题)

取球游戏

题目链接: https://www.lanqiao.cn/problems/278/learning/

题目描述

今盒子里有 nn 个小球,A、B 两人轮流从盒中取球,每个人都可以看到另一个人取了多少个,也可以看到盒中还剩下多少个,并且两人都很聪明,不会做出错误的判断。

我们约定:

每个人从盒子中取出的球的数目必须是:1,3,7 或者 8 个。轮到某一方取球时不能弃权!A
先取球,然后双方交替取球,直到取完。被迫拿到最后一个球的一方为负方(输方)

请编程确定出在双方都不判断失误的情况下,对于特定的初始球数,A 是否能赢?

输入

在这里插入图片描述

输出

程序则输出 nn 行,表示 A 的输赢情况(输为 0,赢为 1)。

样例输入

4
1
2
10
18

样例输出

0
1
1
0
解题:
#include <bits/stdc++.h>
using namespace std;
int main()
{
    
    
	 int a[11000]={
    
    0};
 for(int i=1;i<10000;i++)
 	{
    
    	if(a[i]==0)
 		{
    
    
       a[i+1]=1;
 		a[i+3]=1;
 		a[i+7]=1;
 		a[i+8]=1;
	 }
   }
	
  // 没啥好说的  暴力
  int n;
  cin>>n;

	while(n--)
	{
    
    
		int m;
		cin>>m;
		cout<<a[m]<<endl;
	}
  return 0; 
}


猜你喜欢

转载自blog.csdn.net/qq_54729417/article/details/123675742