16
张炜皓 (ζ͡顾念̶°) LV 5 @ 1 周前
在做这道题前,先来认识一下deque双端队列
C++ STL 中的双端队列
题目连接
使用前需要先引入 头文件。 #include;
STL 中对 deque 的定义
// clang-format off
template<
class T,
class Allocator = std::allocator
class deque;
T 为 deque 中要存储的数据类型。
Allocator 为分配器,此处不做过多说明,一般保持默认即可。
STL 中的 deque 容器提供了一众成员函数以供调用。其中较为常用的有:
元素访问
q.front() 返回队首元素
q.back() 返回队尾元素
修改
q.push_back() 在队尾插入元素
q.pop_back() 弹出队尾元素
q.push_front() 在队首插入元素
q.pop_front() 弹出队首元素
q.insert() 在指定位置前插入元素(传入迭代器和元素)
q.erase() 删除指定位置的元素(传入迭代器)
容量
q.empty() 队列是否为空
q.size() 返回队列中元素的数量
此外,deque 还提供了一些运算符。其中较为常用的有:
使用赋值运算符 = 为 deque 赋值,类似 queue。
使用 [] 访问元素,类似 vector。
头文件中还提供了优先队列 std::priority_queue,因其与堆更为相似,在此不作过多介绍。
来上正解
点击展开代码
点击展开代码
点击展开代码
点击展开代码
点击展开代码
点击展开代码
点击展开代码啊!!!
点击上面啊!
点击上面啊!
点击上面啊!
友情链接
点击上面啊!
------------------------------
初一18班范梓彬 LV 4 @ 1 周前
```
#include
#include
#include
#include
using namespace std;
struct Map{
int x,y,arr[3][3],step;
Map(){
step=0;
}
}st,ed;
dequedq;
bool v[370000];
int front=0,rear=-1;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int Cantor(int a[],int len){
int ans=0;
for(int i=0;i
for(int j=i+1;j
f*=index++;
}ans+=countf;
}return ans;
}
int CantorMap(Map map){
int b[9];
memset(b,0,sizeof(b));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
b[i3+j]=map.arr[i][j];
return Cantor(b,9);
}
int main(){
memset(v,false,sizeof(v));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++){
int temp;
scanf(“%d”,&temp);
if(temp0){st.x=i,st.y=j;}
st.arr[i][j]=temp;
}
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
scanf(“%d”,&ed.arr[i][j]);
v[CantorMap(st)]=true;
//list[++rear]=st;
dq.push_back(st);
int tg=CantorMap(ed);
while(!dq.empty()){
for(int i=0;i<4;i++){
Map map=dq.front();
int xx=map.x+dx[i];
int yy=map.y+dy[i];
if(xx>=0&&yy>=0&&xx<=2&&yy<=2){
swap(map.arr[map.x][map.y],map.arr[xx][yy]);
map.x=xx;
map.y=yy;
map.step++;
}if(v[CantorMap(map)])continue;
if(tgCantorMap(map)){
printf(“%d”,map.step);
return 0;
}//list[++rear]=map;
dq.push_back(map);
v[CantorMap(map)]=true;
}//front++;
dq.pop_front();
}return 0;
}
李咏航 (20221020) LV 8 @ 2 个月前
排版有点乱,建议看(也是我写的)【BFS】八数码问题(c++基础算法)
一.读题
作为最经典的一道宽度优先搜索题,它的题面并不是很难懂。
【宽搜(难度:6)】8数码问题 题目描述 【题意】 在3×3的棋盘上摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围上下左右相邻的棋子可以移到空格中。 现给出原始状态和目标状态,求实现从初始布局到目标布局的最少步骤(初始状态的步数为0)。 如下图,答案为5。
【输入格式】 第一个33的矩阵是原始状态; 第二个33的矩阵是目标状态。 【输出格式】 输出移动所用最少的步数。
【样例1输入】 2 8 3 1 6 4 7 0 5 1 2 3 8 0 4 7 6 5
【样例1输出】 5
【样例2输入】 2 8 3 1 6 4 7 0 5 0 1 2 3 4 5 8 7 6
【样例2输出】 17
很显然,这是要我们求出矩阵1通过白色方块的上下左右移动转化向矩阵2的最小步数。
二.在做题之前
在做题之前,我们先要弄懂3个问题。
1.康拓展开
在这道题中,我们要利用康托展开判断是否重复。在文前,蒟蒻已经写了一篇文章,不懂的可以去看一下:【宽搜必备】康托展开(从公式解析到代码实现)
那么,我们就可以写出:
{
int s=1;
for(int i=1;i<=n;i++)
{
int index=1,f=1,count=0;
for(int j=i+1;j<=n;j++)
{
f*=index;
index++;
if(a[i]>a[j]) count++;
}
s=s+count*f;
}
return s;
}
2.DFS和BFS的区别
bfs 遍历节点是先进先出,dfs遍历节点是先进后出; bfs是按层次访问的,dfs 是按照一个路径一直访问到底,当前节点没有未访问的邻居节点时,然后回溯到上一个节点,不断的尝试,直到访问到目标节点或所有节点都已访问。 bfs 适用于求源点与目标节点距离最近的情况,例如:求最短路径。dfs 更适合于求解一个任意符合方案中的一个或者遍历所有情况,例如:全排列、拓扑排序、求到达某一点的任意一条路径。
3.栈和队列的区别
(1)栈和队列的出入方式不同:栈是后进先出、队列是先进先出。 (2)栈和队列在具体实现的时候操作的位置不同:因为栈是后进先出,它在一段进行操作;而队列是先进先出,实现的时候在两端进行。
现在,我们搞懂了这三个问题,就可以做题了。
三.做题
1.算法原理
采用BFS遍历的方式寻找最优路径。
首先定义一个结构体ma来存放八数码的每一个状态信息,其中包括节点对应的矩阵,节点在BFS遍历树中的深度(相当于步数),以及节点对应的康托值。然后,定义visited数组存放已经访问过的节点状态。
利用队列实现遍历,具体步骤如下:
1.将初始状态的各种信息压入队列中。 2.判断队列是否为空,若为空,退出循环,打印移动步骤,结束。 3.取出队头元素判断是否与目标状态一致。若一致,则退出循环,输出移动步骤,程序结束。若不一致,则分别判断空格向左、向上、向下以及向右能否移动。 5.若可以移动,求其康托值,然后压进队列。并跳转到步骤四。
2.算法实现
①队列
因为此队列要存的东西是一个结构体,因此也要把其类型定为结构体ma
②康托展开
在此代码中,康托展开用于判重。要将一个3*3的矩阵换为一个数。首先,我们要把此二维数组变为一维的。
int d[10],len = 0;
for (int i = 1; i <= 3; i++)
{
for (int j = 1; j <= 3; j++)
{
d[++len] = ak.a[i][j];
}
}
然后,进行康拓转化。最后就是这样
int
{
int d[10],len = 0;
for (int i = 1; i <= 3; i++)
{
for (int j = 1; j <= 3; j++)
{
d[++len] = ak.a[i][j];
}
}
int s=1;
for(int i=1;i<=9;i++)
{
int index=1,f=1,count=0;
for(int j=i+1;j<=9;j++)
{
f=findex,index++;
if(d[i]>d[j]) count++;
}
s=s+countf;
}
return s;
}
③标记
很简单,用数组flag标记康托值即可
四.AC代码
基本队列
好吧,黑历史。。。
#include
using namespace std;
struct node{
int kt,a[10][10],bs,x,y;
}st,ed;
queueq;
bool bo[1000005];
int dx[4]={0,-1,0,1};
int dy[4]={-1,0,1,0};
int cantor(node ha)
{
int b[15],s=0;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
b[3*(i-1)+j]=ha.a[i][j];
}
}
for(int i=1;i<=9;i++)
{
int f=1,index=1,count=0;
for(int j=i+1;j<=9;j++)
{
if(b[i]>b[j]) count++;
f*=index;
index++;
}
s+=count*f;
}
return s;
}
int main()
{
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
scanf(“%d”,&st.a[i][j]);
if(st.a[i][j]==0)
{
st.x=i;
st.y=j;
}
}
}
st.kt=cantor(st);
st.bs=0;
bo[st.kt]=1;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
scanf(“%d”,&ed.a[i][j]);
}
}
ed.kt=cantor(ed);
q.push(st);
while(!q.empty())
{
for(int i=0;i<4;i++)
{
node ne=q.front();
ne.bs++;
ne.x+=dx[i],ne.y+=dy[i];
swap(ne.a[ne.x][ne.y],ne.a[q.front().x][q.front().y]);
ne.kt=cantor(ne);
if(ne.x>=1&&ne.x<=3&&ne.y>=1&&ne.y<=3&&bo[ne.kt]0)
{
q.push(ne);
bo[ne.kt]=1;
if(ne.kted.kt)
{
cout<
}
}
}
q.pop();
}
}
双端队列做法
既然老师教的是双端队列,那么我也更新一下。。。
C++ STL 中的双端队列
此部分转载自zhangweihao的“原创”
使用前需要先引入 头文件。 #include;
STL 中对 deque 的定义
// clang-format off
template<
class T,
class Allocator = std::allocator
class deque;
T 为 deque 中要存储的数据类型。
Allocator 为分配器,此处不做过多说明,一般保持默认即可。
STL 中的 deque 容器提供了一众成员函数以供调用。其中较为常用的有:
元素访问
q.front() 返回队首元素
q.back() 返回队尾元素
修改
q.push_back() 在队尾插入元素
q.pop_back() 弹出队尾元素
q.push_front() 在队首插入元素
q.pop_front() 弹出队首元素
q.insert() 在指定位置前插入元素(传入迭代器和元素)
q.erase() 删除指定位置的元素(传入迭代器)
容量
q.empty() 队列是否为空
q.size() 返回队列中元素的数量
此外,deque 还提供了一些运算符。其中较为常用的有:
使用赋值运算符 = 为 deque 赋值,类似 queue。
使用 [] 访问元素,类似 vector。
头文件中还提供了优先队列 std::priority_queue,因其与堆更为相似,在此不作过多介绍。
代码
#include
using namespace std;
struct ma{
int a[10][10],x0,y0,ans,kt;
};
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
dequeq;
bool flag[4001000];
int kt(ma ak)
{
int d[10],len = 0;
for (int i = 1; i <= 3; i++)
{
for (int j = 1; j <= 3; j++)
{
d[++len] = ak.a[i][j];
}
}
int s=1;
for(int i=1;i<=9;i++)
{
int index=1,f=1,count=0;
for(int j=i+1;j<=9;j++)
{
f=findex,index++;
if(d[i]>d[j]) count++;
}
s=s+countf;
}
return s;
}
int main()
{
ma shi,mo;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
scanf(“%d”,&shi.a[i][j]);
if(shi.a[i][j]0)
{
shi.x0=i,shi.y0=j;
}
}
}
shi.ans = 0;
shi.kt = kt(shi);
flag[shi.kt] = 1;
q.push_back(shi);
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
scanf(“%d”,&mo.a[i][j]);
}
}
mo.kt=kt(mo);
while(!q.empty())//q非空,可以走
{
for(int i=0;i<4;i++)//四个方向
{
ma ac=q.front();
int nx = ac.x0 + dx[i];
int ny = ac.y0+ dy[i];
if(nx>=1&&ny>=1&&nx<=3&&ny<=3)
{
swap(ac.a[ac.x0][ac.y0],ac.a[nx][ny]);
ac.x0=nx;
ac.y0=ny;
//将0与目标数交换
ac.ans++;//步数加1
ac.kt=kt(ac);
//康托判重
if(!flag[ac.kt])
{
flag[ac.kt] = 1;
q.push_back(ac);
//加入队列
if(ac.ktmo.kt)
{
printf(“%d”,q.back().ans);
exit(0);
}
}
}
}
q.pop_front();
//弹出已遍历完所有情况的矩阵
}
}
初一18班范梓彬 @ 1 周前
是真的六,是不是闲得慌
谢信宇 @ 1 周前
大神帮帮我解决那个问题呗`
#include
#include
#include
#include
using namespace std;
struct Map{
int x,y,arr[3][3],step;
Map(){
step=0;
}
}st,ed;
dequedq;
bool v[370000];
int front=0,rear=-1;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int Cantor(int a[],int len){
int ans=0;
for(int i=0;i
for(int j=i+1;j
f*=index++;
}ans+=countf;
}return ans;
}
int CantorMap(Map map){
int b[9];
memset(b,0,sizeof(b));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
b[i3+j]=map.arr[i][j];
return Cantor(b,9);
}
int main(){
memset(v,false,sizeof(v));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++){
int temp;
scanf(“%d”,&temp);
if(temp0){st.x=i,st.y=j;}
st.arr[i][j]=temp;
}
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
scanf(“%d”,&ed.arr[i][j]);
v[CantorMap(st)]=true;
//list[++rear]=st;
dq.push_back(st);
int tg=CantorMap(ed);
while(!dq.empty()){
for(int i=0;i<4;i++){
Map map=dq.front();
int xx=map.x+dx[i];
int yy=map.y+dy[i];
if(xx>=0&&yy>=0&&xx<=2&&yy<=2){
swap(map.arr[map.x][map.y],map.arr[xx][yy]);
map.x=xx;
map.y=yy;
map.step++;
}if(v[CantorMap(map)])continue;
if(tgCantorMap(map)){
printf(“%d”,map.step);
return 0;
}//list[++rear]=map;
dq.push_back(map);
v[CantorMap(map)]=true;
}//front++;
dq.pop_front();
}return 0;
}
5
一中谭智轩 LV 6 @ 1 周前
#include
#include
#include
using namespace std;
struct Map{
int x, y, step, arr[3][3];//arr是棋盘, x是行方位, y是列方位,step是步数。
Map(){
step=0; //步数为0;
}
}st, ed, list[370000];//起点,结束点, 队列;
bool v[370000];
int front = 0,rear = -1;//首指针,尾指针
//向各面走
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};
int Cantor(int a[], int len) {//算当前的排列在全排列中排第几个(康拓值)
int ans=0;
for(int i=0; i a[j]) //算当前未出现的数字中有几个比已知排列的第i位小
count++;
f*=index++;
}
ans+=count*f; //算阶乘
}
return ans;
}
int CantorMap(Map map) { //将二维棋盘化为一维(数列)
int b[9];
memset(b,0,sizeof(b));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
b[i*3+j]=map.arr[i][j]; //因为是3*3的棋盘, so......
return Cantor(b,9); //算康托值, 像名字一样用
}
int main(){
memset(v,0,sizeof(v));
//输入起始状态和最终状态
for(int i=0;i<3;i++)
for(int j=0;j<3;j++) {
int temp;
scanf("%d",&temp); //输入初始的状态
if(temp==0) {
st.x=i,st.y=j; //0(空格)的位置
}
st.arr[i][j]=temp;
}
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
scanf("%d",&ed.arr[i][j]); //输入目标状态
}
}
v[CantorMap(st)]=true; //将这个状态的康拓值命名为已用
list[++rear] = st; //入队
int tg = CantorMap(ed); //将结束值化为'名'
while(front<=rear) { //如果队列未清空
for(int i=0;i<4;i++) {
Map map = list[front];//将当前状态移到队首
//试方案
int xx = map.x+dx[i];
int yy = map.y+dy[i];
if(xx>=0 && xx<=2 && yy>=0 && yy<=2) {
//移动,也就是交换
swap(map.arr[map.x][map.y],map.arr[xx][yy]);
map.x=xx;
map.y=yy;
map.step++; //步数+1;
}
//如果已经走过了,就不走了
if(v[CantorMap(map)]) continue;
//如果当前状态等于目标状态,就输出步数
if(tg==CantorMap(map)) {
printf("%d",map.step);
return 0;
}
list[++rear] = map;//不然就再次移动(改变状态)
v[CantorMap(map)] = true; //已用
}
front++; //出队,换下种状态试。
}
return 0;
}
宽搜的试方案顺序 (注意:每方案表示一个状态) 按此数字从小到大试
1
2 3 4 5
6 7 8 9 10 11 1213 14151617 18192021
还有很多分杈(上图省略了)
本题中宽搜是一个四杈数 !!!
但因为这么多状态中有很多是重复的, so要用 bool v数组来避免走重复的状态
试完一个状态后, 如果他不是目标状态,将他出队, 并将他下面的四杈入队。然后继续是如图中同行的下一个状态
假设 ‘+ ’表试入队, ‘- ’表示出队, ‘e ’表示试一次方案, , '1e' 表示试一次方案一 , 那么上图的试状态顺序如下 (一直没试到方案的情况)
+1 1e -1 +2+3+4 2e +6+7+8+9 -2 3e +10+11+12+13 -3 e4 +14+15+16+17 -4 e5 +18+19+20+21 -5 e6......
一中谭智轩 @ 1 周前
由于hydro的题解编辑实在是一言难尽,所以这题解有点......
李咏航 (20221020) @ 1 周前
@ 一中谭智轩 是MarkDown
一个 垃圾 很好的编译器啊!
3
谢东阳 (20221243) LV 7 @ 1 周前
宽搜这东西,无非就是...
1: O
/ \
2: O O
/ \ / \
3: O O O O
....................
很多人都不理解一个东西(比如曾经的我) 那就是... 西天的东方不败:
struct Map{
int x,y,step,arr[3][3];
Map() {
step=0;
}
}st,ed,list[440000];
list:对的,那个东方不败就是我。 虽然我们已经学了deque, 这个长得奇奇怪怪的东西...
但
必须要理解宽搜的原理,这个list是什么? (list:*******) 已经知道了宽搜的遍历顺序了,但是怎么实现呢? 众所周知,dfs是通过函数里面写函数来进行单个遍历的,如果写在一个main函数里面,就是一堆while。 但是宽搜不一样啊,这遍历顺序可比dfs难表达。 这时候,list它他(list本人要求用单人旁的ta)登场了! 有list之前,宽搜是这样的!
1: O
/ \
2: O O
/ \ / \
3: O O O O
....................
我们还是标上序号吧!
一: 1
/ \
二: 2 5
/ \ / \
三: 3 4 6 7
....................
我这里的序号,是用dfs的顺序写的。 好了,用dfs的遍历顺序,就是...
1 2 3 4 5 6 7
那用bfs呢?就是这样的!
list[1] list[2] list[3] ...
(list:*******) 搞错了,再来:
1 2 5 3 4 6 7
OK啊,那么list,就是对应了每一个数字,每一个list,都是这个队列中的一个,酱紫,就可以解决顺序问题啦! (list此时正得意洋洋的看着你)
但!
list已经成为了过去,现在,是deque的时代。 (deque: dide que shi de) (list:T-T)
虽然用了deque,就不用list了,而且list的两个小弟front和rear也会离开。 但他们成为了deque的一员,在数字生命世界deque中活着... 好了,宽搜的大致原理讲完了,具体怎么做,就交给代码了。
什么?你还要看代码? 21世纪了,还有人想看代码? 看别人去吧,这个世界不缺代码...(代码:qwq)
点赞不点赞自己选,反正...(懂得都懂)
谢东阳 (20221243) @ 1 周前
写的有点模糊,只是按自己的理解写了大概原理,似乎废话有点多...见谅。
3
初一18班范梓彬 LV 4 @ 1 周前
【宽搜(难度:6)】8数码问题 题目描述 【题意】 在3×3的棋盘上摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围上下左右相邻的棋子可以移到空格中。 现给出原始状态和目标状态,求实现从初始布局到目标布局的最少步骤(初始状态的步数为0)。 如下图,答案为5。
【输入格式】 第一个33的矩阵是原始状态; 第二个33的矩阵是目标状态。 【输出格式】 输出移动所用最少的步数。
【样例1输入】 2 8 3 1 6 4 7 0 5 1 2 3 8 0 4 7 6 5
【样例1输出】 5
【样例2输入】 2 8 3 1 6 4 7 0 5 0 1 2 3 4 5 8 7 6
【样例2输出】 17
#include
#include
#include
using namespace std;
struct Map{
int x,y,arr[3][3],step;
Map(){
step=0;
}
}st,ed,list[370000];
bool v[370000];
int front=0,rear=-1;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int Cantor(int a[],int len){
int ans=0;
for(int i=0;ia[j])
count++;
f*=index++;
}
ans+=count*f;
}
return ans;
}
int CantorMap(Map map){
int b[9];
memset(b,0,sizeof(b));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
b[i*3+j]=map.arr[i][j];
return Cantor(b,9);
}
int main(){
memset(v,false,sizeof(v));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++){
int temp;
scanf("%d",&temp);
if(temp==0){
st.x=i,st.y=j;
}st.arr[i][j]=temp;
}
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
scanf("%d",&ed.arr[i][j]);
v[CantorMap(st)]=true;
list[++rear]=st;
int tg=CantorMap(ed);
while(front<=rear){
for(int i=0;i<4;i++){
Map map=list[front];
int xx=map.x+dx[i];
int yy=map.y+dy[i];
if(xx>=0&&xx<=2&&yy>=0&&yy<=2){
swap(map.arr[map.x][map.y],map.arr[xx][yy]);
map.x=xx;
map.y=yy;
map.step++;
}
if(v[CantorMap(map)]) continue;
if(tg==CantorMap(map)){
printf("%d",map.step);
return 0;
}
list[++rear]=map;
v[CantorMap(map)]=true;
}
front++;
}
return 0;
}
好评
2
谢信宇 LV 5 @ 1 周前
SOS
此蒟蒻问问各位大神如何不用康托的代码去实现deque双端队列 下面给出的代码是我的,但样例过了,递交的时候有两个是TLE和MLE,我相信你们的脑瓜子能用上述方法做出来的
#include
#include
#include
#include
using namespace std;
struct Map{
int x,y,arr[3][3],step;
Map(){
step=0;
}
}st,ed;
deque