【探究图论中dfs记忆化,搜索,递推,回溯关系】跳棋,奶牛隔间, 小A和uim之大逃离 II

本篇很高能,如有错误欢迎指出,本人能力有限(需要前置知识记忆化dfs,树形dp,bfs+dp,tarjan)

另外,本篇之所以属于图论,也是想让各位明白,dfs就是就是在跑图!如果dfs离开了图论的知识将会困难重重

记忆化dfs可以看这里

【算法每日一练]-记忆化dfs (保姆级教程 篇4)#滑雪 #天下 第一 #切木棍-CSDN博客

树形dp可以看这里

【算法每日一练]-动态规划 (保姆级教程 篇6(树形dp))-CSDN博客

tarjan可以看这里(这个是重点)

【看不懂你来打我]-图论(保姆级教程篇11 tarjan)无向图的桥 ,无向图的割点 ,有向图的强连通分量-CSDN博客

        

先来题目引出问题:

题目:跳棋

         

碎碎念部分:(如果你有兴趣可以看一下,如有错误欢迎指出,本人能力有限)

题意就是从0的地方选四个方向,跳到下一个0的地方,重复,问问你最远能走多远?

那么我就寻思好嘛,太常见了:我反手就是f[x][y]=max(dfs[下一个0坐标]+当前0坐标到下一个0坐标的距离),然后设置f[x][y]表示从当前点出发能走的最远距离。这样的话还能记忆化加速,我去,我可太聪明了!下面是我的伪代码:

for(4个方向)
{   
    先获取该方向下一个0点坐标;
    if(该坐标存在且该坐标并没有走过)
        {   vis[下个坐标]=1;
            d=dfs(下一个坐标)+两点坐标距离;
            vis[下个坐标]=0;
            if(f[x][y]<d)f[x][y]=d;
        }
    }
}

然后外面的dfs再加上记忆化和返回值步骤即可。欧了,输入样例---------跑的什么玩意???

        

其实这段代码问题很大!

首先就是vis数组和dfs(下个状态)非常冲突,因为你设置的f[x][y]表示以此为起点去跑,可是你在dfs下一个状态时候的它的vis都不是清空的,它的返回的f结果怎么可能会是对的呢?想要使得下一个状态的结果是正确的就应该让它以起点单独跑,你以为这样就行了?

还是错!因为它的下一个状态还会遇到相同的问题,那么返回的结果也不对,(那不无解了吗

还有一点是记忆化那里:if(f[此状态来过])return f[此状态]。

这句话也不对,因为它的前提是你的f状态的结果是正确的,如果现在还不是正确的,那不应该继续跑它吗?而不是直接去使用呀,所以这句话也不能有!

以上的思路都是来自之前做过的一道滑雪的题。(在开头哪里有,你可以去看一看)

然后我们来对比一下之前做过的“ 滑雪 ”那道记忆化题:

在那个题中,我们设置f[x][y]表示从此点为起点跑的最远距离,然后有f[x][y]=max(f[x][y],dfs(下一个点)+1),之所以这个式子是正确的,是因为它后面的dfs(下一个点)的结果是正确的!那为什么下一个点的dfs是正确的呢?是因为它下一个dfs的结果是正确的,那么为什么它下一个结果是也是正确的呢?是因为它每个下层状态都不依赖于前面的dfs结果,也就是没有环!也正因为没有环,这个dfs的结果一定是正确的。也就是不会改变的,既然都不会再改变,那以后再遇到这种情况还跑啥呀直接使用结果呗,所以就可以记忆化去省时间,它的模式是类似树形dp的,就是不会遇到环。

到这里,你就发现了本题出问题的原因是有环!也就是你的下一个状态要想正确的跑出来,就依赖于之前的状态,但是之前状态的正确性又要靠后面去跑,所以这样去设置f[x][y]的含义是非常不应该的。所有有环的dp都非常危险,无论是你是循环dp还是dfsdp,都不是很妥的。而树形dfs一般都可以来dp,也可以记忆化。

另外,递推一般可以记忆化,搜索当然也可以记忆化,而有环一般就不行了。当然有环一般伴随着回溯。

说了这么多。赶紧回来回来

思路:

本题明显适合搜索,而不是递推。

我们可以直接去搜索跑的,并不会超时,每dfs一个点就先更新一下答案,然后找到下一个0点坐标,如果有的话且没有走过就跳过去,然后从那个点继续跑,回来时候再清空标记。重复。

一套流程行云流水就打出来了。代码如下:

#include <bits/stdc++.h>
using namespace std;
int n,k,ans;
int m[105][105],f[105][105];//f来标记是否来过
int dx[]={-1,1,0,0},dy[]={0,0,1,-1};
void dfs(int x,int y,int step){
	ans=max(ans,step);//更新答案
	for(int i=0;i<4;i++){
		int tx=x,ty=y,s=0;
		while(tx+dx[i]>0&&tx+dx[i]<=n&&ty+dy[i]>0&&ty+dy[i]<=n){
			tx+=dx[i];ty+=dy[i];//不断沿着这个向量前进
			s++;//获取两点距离,注意至少超越一下,s最少是2!
			if(m[tx][ty]==0)break;
		}
		if(tx>0&&tx<=n&&ty<=n&&ty>0&&f[tx][ty]==0&&m[tx][ty]==0&&s!=1){
			f[tx][ty]=1;
			dfs(tx,ty,step+s);//搜下一个点
			f[tx][ty]=0;
		}
	}
}
int main(){
	int x,y;
	cin>>n>>x>>y;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)cin>>m[i][j];
	f[x][y]=1;//标记出发点被走过了
	dfs(x,y,0);//开始搜索
	cout<<ans<<'\n';
	return 0;	
}

 

        

        

 题目:奶牛隔间

        

一开始思路是模拟,后来看到隔间数和奶牛数……好吧不行,那应该就是dp了,循环dp我不太会写,dfsdp应该可以的,好的,那么我们开始写:

设置f[x]表示从x开始访问的隔间数,那么因为从x隔间走,访问的隔间数是一定的,故而可以记忆化节省时间,那么反手就是:

f[x]=dfs(下一个隔间)+1;然后这个式子我是越看越迷糊,下一个隔间要想成功遍历,和上一个状态很冲突啊!因为从下一个隔间为起点的话,当前的隔间x就不能被标记呀,看到了吗?下一个状态会跑到前一个状态,你告诉我这能dp?

思路:

根据题意,一只奶牛停止的条件是来到她所经过过的房间,也就是奶牛想要停下来必须要找到一个环。看到了吧,这是有环的,那么我们也有tarjan啊。来吧!

首先明显是有向图,我们跑一下tarjan把那些环划到一起,然后把环看成一个整体,或者把整个图看成是许多个强连通分量(为什么?因为无论这个环从哪个点进入,返回结果都是一样,都是环长),我们在这些强联通分量之间建立指向关系,然后就可以树形dp了,当然也需要记忆化(不然还是超时)另外提示一下:强联通分量中节点数就是环上点的个数。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int num,n,be[N],to[N],out[N],dfn[N],low[N],dp[N],sz[N];
bool ins[N];
stack<int>s;
void tarjan(int u){
	dfn[u]=low[u]=++num;
	ins[u]=1;
	s.push(u);
	int v=to[u];
	if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
	else if(ins[v]) low[u]=min(low[u],dfn[v]);
//	for(int i=0,sz=ve[u].size();i<sz;i++){//老模版了
//		int v=ve[u][i];
//		if(!dfn[v]){
//			tarjan(v);low[u]=min(low[u],low[v]);
//		}
//		else if(ins[v]){
//			low[u]=min(low[u],dfn[v]);
//		}
//	}
	if(low[u]==dfn[u]){
		int v;
		do{
			v=s.top();s.pop();
			be[v]=u;
			ins[v]=0;
			sz[u]++;//顺便统计这个环(强联通分量)中有多少个点
		}while(v!=u);
	}
}
int dfs(int u){
	if(u==0||dp[u])return dp[u];//记忆化
	return dp[u]=dfs(out[u])+sz[u];//树形dp
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%d",&to[i]);
	for(int i=1;i<=n;i++){
		if(!dfn[i])tarjan(i);
	}
	for(int i=1;i<=n;i++){
		if(be[i]!=be[to[i]])
			out[be[i]]=be[to[i]];//因为出边只有一个,所以这样建边
	}
	for(int i=1;i<=n;i++)
	printf("%d\n",dfs(be[i]));
	return 0;
}

        

        

题目:小A和uim之大逃离 II

每个点都有两种状态,问你能不能走出去,可能有人想循环dp,但是这个绝对不能循环dp。

因为循环dp的顺序非常有问题,导致在转移的时候有多点还没有更新就已经被转移了,是必错的结局!那么正解是什么呢?

我认为bfs+dp是最好的,因为bfs是按层跑的,dp应该按层去转移,才是最正确的!

思路:

对于本题,每个点都有两种,如果只有一种,那么就很好转移;但是如果有两种,那么不妨就保存两种点。       
我们bfs是按层跑的,不放设置st[x][y][u]表示走到(x,y)点且没有嗑药的最小步数(u=0),表示走到(x,y)点且已嗑药的最小步数(u=1)

当从当前点cur.x和cur.y准备走到下个点x,y时:

如果到下一点不嗑药,无论u是0还是1,那么都是st[x][y][cur.u]=st[cur.x][cur.y][cur.u]+1。然后入队
如果到下一点再嗑药,那么就是st[x+d][y+r][1]=st[x][y][0]+1。然后入队

千万注意顺序,一定是先不嗑药在前面,把st[x][y][0]更新正确,然后才是考虑这个点嗑药。你当然可以理解成嗑药的话相当于走了两步!(也没有人说bfs的所有点都只能一次走一步啊)

补充:

你会发现这些转移都是具有唯一性的,也就是说仅转移一次。
直观理解:上面是不嗑药的平面点集,u全是0,下面是嗑药的平面点集,u全是1。在跑bfs的时候,我们允许发生点从上面跑到下面,但是不能从下面到上面。
而且下面的点要么是由前面转移过来,要么是从上面点转移过来,只有这两种情况,同时分别对应不嗑药和嗑药。至此,bfs+dp验证成立!

#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int h,w,d,r,st[N][N][2],dx[]={0,0,1,-1},dy[]={1,-1,0,0};
char s[N][N];
struct node {int x,y,u;};
bool check(int x,int y){return x>=1&&y>=1&&x<=h&&y<=w&&s[x][y]=='.';}
int main(){
	cin>>h>>w>>d>>r;
	for(int i=1;i<=h;i++)
	scanf("%s",s[i]+1);//这个写法太妙了!!!一定要会啊
	memset(st,-1,sizeof(st));
	st[1][1][0]=0;//初始化
	queue<node>q;
	q.push(node{1,1,0});
	while(!q.empty()&&st[h][w][0]==-1&&st[h][w][1]==-1){//有点跑到终点时候就可以前提停了
		node cur=q.front();q.pop();
		for(int i=0;i<4;i++){
			int x=dx[i]+cur.x,y=dy[i]+cur.y;
			if(check(x,y)&&st[x][y][cur.u]==-1){
				q.push((node){x,y,cur.u});//不嗑药的点入队
				st[x][y][cur.u]=st[cur.x][cur.y][cur.u]+1;//既打标记,又存入答案
				if(cur.u==0&&check(x+d,y+r)&&st[x+d][y+r][1]==-1){
					q.push((node){x+d,y+r,1});//嗑药的点入队
					st[x+d][y+r][1]=st[x][y][0]+1;
				}
			}
		}
	}
	if(st[h][w][0]==-1&&st[h][w][1]==-1)cout<<"-1";
	else{
		if(st[h][w][0]!=-1&&st[h][w][1]!=-1)cout<<min(st[h][w][0],st[h][w][1]);
		else {
			if(st[h][w][0]==-1)cout<<st[h][w][1];
			else cout<<st[h][w][0];
		}
	}
}

看到这里,你果然是高手。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/490184.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DNS 服务 Unbound 部署最佳实践

文章目录 安装unbound-control配置启动服务测试 参考&#xff1a; 官网地址&#xff1a;https://nlnetlabs.nl/projects/unbound/about/ 详细文档&#xff1a;https://unbound.docs.nlnetlabs.nl/en/latest/index.html DNS服务Unbound部署于使用 https://cloud.tencent.com/…

cryptography,一个神奇的 Python 库!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个神奇的 Python 库 - cryptography。 Github地址&#xff1a;https://github.com/pyca/cryptography 在当今数字化时代&#xff0c;信息安全越来越受到重视。数据加密是保护…

海外媒体发稿:9种高效的媒体套餐内容发稿策略分析-华媒舍

海外媒体发稿&#xff1a;9种高效的媒体套餐内容发稿策略分析高效的媒体发布和营销推广策略对公司、本人的成就尤为重要。下面我们就对于媒体套餐内容发稿营销推广策略开展全面解析&#xff0c;帮助读者掌握并应用这9种合理的思路&#xff0c;进而获得更好的媒体营销效果。 1.媒…

基于react native的自定义轮播图

基于react native的自定义轮播图 效果示例图示例代码 效果示例图 示例代码 import React, {useEffect, useRef, useState} from react; import {Animated,PanResponder,StyleSheet,Text,View,Dimensions, } from react-native; import {pxToPd} from ../../common/js/device;c…

小目标检测篇 | YOLOv8改进之GSConv + Slim Neck提升小目标检测效果

前言:Hello大家好,我是小哥谈。在文章中,作者提出了一种新方法GSConv来减轻模型的复杂度并保持准确性。GSConv可以更好地平衡模型的准确性和速度。并且,提供了一种设计范式Slim Neck,以实现检测器更高的计算成本效益。实验过程中,与原始网络相比,改进方法获得了最优秀的…

GDAl 之绘制栅格图像的大致直方图和精准直方图(8)

gdal的绘制大致直方图是仅查看概览或者抽样像素的一个子集 import os from osgeo import gdal import matplotlib.pyplot as plt import numpy as np# Dont forget to change directory. os.chdir(rD:\DeskTop\learn_py_must\Learn_GDAL\osgeopy-data\osgeopy-data\Switzerlan…

基于Selenium+Python的web自动化测试框架!

简介&#xff1a; 本文将详细介绍如何运用Python结合Selenium WebDriver库搭建web自动化测试框架。 一、什么是Selenium&#xff1f; Selenium是一个基于浏览器的自动化测试工具&#xff0c;它提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分…

音视频处理 - 音频概念详解,码率,采样率,位深度,声道,编码

1. 音频采样 与视频不同&#xff0c;音频的最小单位不是一帧&#xff0c;而是一个采样。 采样是当前一刻声音的声音样本&#xff0c;样本需要经过数字转换才能存储为样本数据。 真实声音是连续的&#xff0c;但是在计算机中&#xff0c;声音是离散且均匀的声音样本。 2. 位深…

ER图与关系模型

1.设某商业集团数据库中有三个实体集。 “公司”实体集&#xff0c;属性有公司编号、公司名、地址等&#xff1b; “仓库”实体集&#xff0c;属性有仓库编号、仓库名、地址等&#xff1b; “职工”实体集&#xff0c;属性有职工编号、姓名、性别等。公司与仓库间存在“隶属…

《被讨厌的勇气》读书思考笔记 (好书推荐)

《被讨厌的勇气》是一本由日本心理学家岸见一郎和奥冈昌高合著的畅销心理成长书籍。这本书基于心理学家阿尔弗雷德阿德勒的思想&#xff0c;介绍了“自我决定心理学”的观点&#xff0c;探讨了人们如何克服害怕失败&#xff0c;勇敢追求自己真正想要的生活。这本书在心理学、自…

HCIP的学习(4)

GRE和MGRE VPN---虚拟专用网络。指依靠ISP&#xff08;运营商&#xff09;或其他公有网络基础设施上构建的专用的安全数据通信网络。该网络是属于逻辑上的。​ 核心机制—隧道机制&#xff08;封装技术&#xff09; GRE—通用路由封装 ​ 三层隧道技术&#xff0c;并且是属于…

Git基础(23):Git分支合并实战保姆式流程

文章目录 前言准备正常分支合并1. 创建两个不冲突分支2. 将dev合并到test 冲突分支合并1. 制造分支冲突2. 冲突合并 前言 Git分支合并操作 准备 这里先在Gitee创建了一个空仓库&#xff0c;方便远程查看内容。 正常分支合并 1. 创建两个不冲突分支 &#xff08;1&#xf…

Tableau项目案例-网上超市运营分析

一、数据简要介绍 超市运营分析.xlsx 1、客户分析 交易次数统计 购买次数即购买频率,是指消费者在一定时期内购买某种或某类商品的次数。 用tableau打开excel文件 双击城市字段,会显出出一个地图 类别字段也拖到筛选器上,如上操作相同

AI论文速读 | 具有时间动态的路网语义增强表示学习

论文标题&#xff1a; Semantic-Enhanced Representation Learning for Road Networks with Temporal Dynamics 作者&#xff1a; Yile Chen&#xff08;陈亦乐&#xff09; ; Xiucheng Li&#xff08;李修成&#xff09;; Gao Cong&#xff08;丛高&#xff09; ; Zhifeng Ba…

管理能力学习笔记四:团队发展四阶段

组建期 管理方式 动荡期 领导方式 规范期 管理方式 高产期 管理方式 高产期的注意点

FL Studio21.2.3最新中文编曲音乐制作软件新版本功能介绍

一、前言 随着科技的发展&#xff0c;越来越多的人开始尝试自己创作音乐。然而&#xff0c;传统的音乐制作过程复杂繁琐&#xff0c;需要昂贵的硬件设备和专业的知识技能。那么&#xff0c;有没有一款软件可以让普通人也能轻松地制作出专业级别的音乐作品呢&#xff1f;答案就…

什么是 ECMAScript,它与 JavaScript 有何不同

什么是 ECMAScript? 关于 JavaScript](https://cloudaffle.com/history-of-javascript/)的[历史以及它是如何产生的,有一个完整的故事。长话短说,ECMAScript 中的 ECMA 是指欧洲计算机制造商协会,早在 1997 年就向该协会提交了 JavaScript 1.1 进行标准化。创建了一个技术委员…

机器学习(二)

线性模型: 离散转为连续的变换: 检查是否有“序”的变化&#xff0c;若有“序”&#xff0c;则连续化&#xff1b;否则&#xff0c;转化为k维向量 最小二乘解: 多元线性回归: 广义线性模型: 线性判别分析: 由于将样例投影到一条直线(低维空间)&#xff0c;因此也被视为一种&q…

洛谷1803

P1803 凌乱的yyy / 线段覆盖 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 所需知识&#xff1a;贪心 本来还想用dfs bfs搜索来一点一点做的&#xff0c;看到了大佬的思路之后&#xff0c;直接orz了 整体思路&#xff1a;因为要想尽可能的多参加比赛&#xff0c;所以越早结…

MySQL 经典练习 50 题 (记录)

前言&#xff1a; 记录一下sql学习&#xff0c;仅供参考基本都对了&#xff0c;不排除有些我做的太快做错了。里面sql不存在任何sql优化操作&#xff0c;只以完成最后输出结果为目的&#xff0c;包含我做题过程和思路最后一行才是结果。 1.过程: 1.1.插入数据 /* SQLyog Ul…