2022.1.10 学习总结

        今天解出两道洛谷上面的搜索题,分别是“kkksc03考前临时抱佛脚”与“填涂颜色 - 洛谷

题目:

kkksc03考前临时抱佛脚

由题目意思可知,该题是要求我们将同一科目的所有“完成习题册”的时间尽可能均衡地分配给左右脑(双核就是强),然后选取各个科目的耗时较多的部分,相加就是正确答案。

说起来很简单,就像一道简单的贪心类水题,但实际上需要用到动态规划,主要是解法类似于动态规划里的经典例题“01背包”。(而动态规划0基础的我足足花了一整个上午才勉强明白)

动态规划学习笔记如下

类似于函数的递归使用,只是在这里是用数组的形式表现,同时也要精巧不少。

回到题目。既然按题意来说要使左右脑所分配的时间尽可能相近,那么在这题里就可以把总时间的1/2当作背包总容量,随后利用循环逐个判断“该物品能不能装进背包”,“装进这个物品得到的收益大还是不装大”,最后得到的数值一定是最接近总时间的1/2同时又小于总时间的1/2的时间

#include<stdio.h>
int s[4],z[4],dp[2000],a[100];
int sum;
int max(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	int i,j,k;
	for(i=0;i<4;i++)
	scanf("%d",&s[i]);//s[i]表示第i门科目的习题集个数 
	for(i=0;i<4;i++)
	{
		for(j=0;j<s[i];j++)
		{
		scanf("%d",&a[j]);
		z[i]+=a[j];
	    }
	    for(j=0;j<s[i];j++)
		{
			for(k=z[i]/2;k>=0;k--)
			{
				//这里只需要考虑可以取的情况,因为若是无法取到,可取物品数量-1,而在循环中k--就直接表示“可取物品-1”
				if(k>=a[j])//当 k>=a[j]时,即背包容量大于当前选中物品的体积,那么需要判断取与不取两种情况,谁的可能收益最大 
				{
					dp[k]=max(dp[k],dp[k-a[j]]+a[j]);
				}
			}
		}
		//循环结束后,此时dp内存储着最接近z[i]/2同时小于z[i]/2的物品,那么取左右脑中较大的数,即:“z[i]-dp[z[i]/2]” 
		sum+=z[i]-dp[z[i]/2];
		for(j=0;j<z[i];j++)
		dp[j]=0;
	}
	printf("%d",sum);
	return 0;
}

得到dp后拿总时间减去这个dp得到的就是正确答案了

题目:
填涂颜色

这题的题意很好理解:给你一个“地图”,将其中闭合圈内的数字“0”变成“2”。

只要注意一下“闭合圈”的定义就好了。例如一个矩阵

1 1 1 0 0 0

1 0 1 1 1 1

1 1 1 0 0 1

0 0 1 1 1 1

(这个矩阵只是我举个例子,题目里给出的矩阵是个四边等长的正方形矩阵)

能变成“2”的只有第2行和第3行的0,也就是“半包围”的“0”不算闭合圈内的数值。

明白了这点就很好做了

我的想法是将所有圈外0全部标记为-1,最后再遍历地图,将0变成2,-1变成0就可以了。

#include<stdio.h>
int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右上左下 
int a[50][50];
int n;
void dfs(int x,int y)
{
	int i,j;
	if(a[x][y] != 0)
	return ;
	if(a[x][y] == 0 )
	a[x][y]=-1;
	
	if(x < 1 || x > n || y < 1 || y > n )
	return;
	for(i=0;i<4;i++)
	{
		int tx,ty;
		tx=x+next[i][0];
		ty=y+next[i][1];
		dfs(tx,ty);
	}
	
}
int main()
{
	scanf("%d",&n);
	int i,j;
	for(i=1;i<=n;i++)
	   for(j=1;j<=n;j++)
		scanf("%d",&a[i][j]);
	
	for(i=1;i<=n;i++)
	{
	dfs(i,1);
	dfs(1,i);
	dfs(n,i);
	dfs(i,n);
    }
	int k=2,flag=0;
	while(k--)
{
	for(i=1;i<=n;i++)
	{
	   for(j=1;j<=n;j++)
	   {
	   	if(a[i][j]== 0 && flag==0)
	   	a[i][j]=2;
	   	if(a[i][j]== -1 && flag==1)
	   	a[i][j]=0;
	   }
    }
    flag=1;
}
	for(i=1;i<=n;i++)
	{
	   for(j=1;j<=n;j++)
	   printf("%d ",a[i][j]);
	   printf("\n");
    }
	return 0;
}

  看书笔记:

今天学长讲了一下dfs、bfs以及快排

我也在《啊哈算法》上跟进学习了这部分内容

以迷宫一题为例,将输出目标改为最少步数

下面是啊哈算法dfs模板代码

#include<stdio.h>
int n,m,p,q,min=999999;
int a[51][51],book[51][51];
void dfs(int x,int y,int step)
{
	int next[4][2]=
	{{0,1},//向右走 
	{1,0},//下 
	{0,-1},//左 
	{-1,0}};//上 
	int tx,ty,k;
	if(x == p && y == q)//判断是否到达XX的位置 
	{
		if(step < min)//更新最小值 
		min=step;
		return ;
	}
	//枚举四种走法 
	for(k=0;k<=3;k++)
	{
		//计算下一个点的坐标 
		tx=x+next[k][0];
		ty=y+next[k][1];
		//判断是否越界
		if(tx<1 ||tx>n ||ty<1 ||ty>m)
		{
			continue;
		} 
		//判断下一步的点是否为障碍物或者在该路线中已经走过
		if(a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty]=1;//标记这个点已经走过 
			dfs(tx,ty,step+1);//开始尝试下一个点 
			book[tx][ty]=0;//尝试结束,取消该点标记 
		} 
	}
	return ; 
}
int main()
{
	int i,j,startx,starty;
	//读入n和m分别为行与列
	scanf("%d %d",&n,&m);
	//读入迷宫
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
		}
	}
	//读入起点和终点坐标
	scanf("%d %d %d %d",&startx,&starty,&p,&q);
	//从起点开始搜索
	book[startx][starty]=1;//标记起点已在路径中,防止后面重复走
	//第一个参数是起点的x坐标,第二个参数是起点的y坐标,第三个参数是初始步数为0 
	 dfs(startx,starty,0);
	 
	 //输出最短步数
	 printf("%d",min);
	 getchar();
	 return 0; 
}

Dfs的使用方法与使用思路类似于动态规划:“找出前后任务之间的关系,并一般都是通过递归实现“,同时dfs也是一种穷举,只不过是由计算机完成这个”穷举“后按照指令给出目标答案而已。

而对于“找出前后任务的关系并通过递归实现”这一项,可以形象理解为树的遍历,由树的主干逐步遍历到树的所有分支及节点,而后对于达到程序目标的进行相应的处理,例如“完成方案+1”、“最小路径判断”等等

因为刚刚说过dfs也是一种穷举,故时间复杂度方面,最坏的情况应该是O(!n)

同样以迷宫一题为例

下面是《啊哈算法》bfs模板代码

#include<stdio.h>
struct note
{
	int x;//横坐标 
	int y;//纵坐标 
	int f;//XX在队列中的编号,本题不要求输出路径,可不要f 
	int s;//步数 
}; 
int main()
{
	struct note que[2501];//地图大小不超过50*50,因此队列扩展不超过2500个
	int a[51][51]={0},book[51][51]={0};//a[][]用来存储地图
	//book[][]用来记录哪些点已经在队列中了,防止一个点重复扩展 
	//定义一个用于表示走的方向的数组
	int next[4][2]=
	{{0,1},//右
	{1,0},//下
	{0,-1},//左
	{-1,0}};//上
	int head,tail;
	int i,j,k,n,m,startx,starty,p,q,tx,ty,flag;
	scanf("%d %d",&n,&m);
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		scanf("%d",&a[i][j]);
	}
	scanf("%d %d %d %d",&startx,&starty,&p,&q);
	//队列初始化
	head=1;
	tail=1;
	//往队列插入迷宫入口坐标
	que[tail].x=startx;
	que[tail].y=starty;
	que[tail].f=0;
	que[tail].s=0;
	tail++;
	book[startx][starty]=1;
	flag=0;//用来标记是否到达目标点,0表示暂时还没有到达,1表示到达
	//当队列不为空时循环
	while(head<tail)
	{
		//枚举四个方向
		for(k=0;k<=3;k++)
		{
			//计算下一个点的坐标
			tx=que[head].x+next[k][0];
			ty=que[head].y+next[k][1];
			//判断是否越界
			if(tx < 1 || tx > n || ty < 1 || ty > m)
			continue;
			//判断是否为障碍物或者该点已在路径中
			if(a[tx][ty] == 0 && book[tx][ty] == 0)
			{
				//将该点标为“已走”
				//注意宽搜每个点只入队一次,所以和深搜不同,不需要将book数组还原
				book[tx][ty]=1;
				//插入新的点到队列中
				que[tail].x=tx;
				que[tail].y=ty;
				que[tail].f=head;//因为该点是从head扩展出来的,所以它的父亲是head,本题不需要路径,故本句可省
				que[tail].s=que[head].s+1;//步数是父亲的步数加一 
				tail++; 
			 }
			 //如果到目标点了,停止扩展,任务结束,退出循环
			 if(tx == p && ty == q)
			 {
			 	flag=1;
			 	break;
			  } 
		} 
		if(flag == 1)
		break;
		head++;
		//当一个点扩展结束后,head++才能对后面的点再进行扩展 
	}
	//打印队列中末尾最后一个点(目标点)的步数
	//注意tail是指向队列队尾(即最后一位)的下一个位置,所以这需要-1
	printf("%d",que[tail-1].s);
	getchar();//getchar(); 
	return 0;
}
/*
5 4
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
1 1 4 3
输出:7
*/ 

 相对dfs而言,bfs相对要更难理解与使用一些。bfs主要思想是使用队列存储走过的路径,利用循环遍历四个方向可能得到的结构,循环遍历方向结束后发现头元素可以到达的位置已经全部入队,此时头元素已经没有了作用,那么就可以弹出头元素,接着判断由上一个点扩展出来的点位是否到达目的地,若没有到达则继续上述循环。

而与dfs最明显的不同是:

dfs是一步一步试探,如果不能达成目标就会回溯至前一个状态重新考虑

而bfs则是将每一步都扩展开来,存储它们的扩展点位,如此循环反复,最后一定会有至少一个点位到达目的地,这样就免去了回溯的步骤。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值