题目解析2024.3.29

P1044 [NOIP2003 普及组] 栈

栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。

栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。

栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。

题目描述

宁宁考虑的是这样一个问题:一个操作数序列,1,2,…,�1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 �n。

现在可以进行两种操作,

  1. 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
  2. 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)

使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由 1 2 3 生成序列 2 3 1 的过程。

(原始状态如上图所示)

你的程序将对给定的 �n,计算并输出由操作数序列 1,2,…,�1,2,…,n 经过操作可能得到的输出序列的总数。

输入格式

输入文件只含一个整数 �n(1≤�≤181≤n≤18)。

输出格式

输出文件只有一行,即可能输出序列的总数目。

输入输出样例

输入 #1复制

3

输出 #1复制

5

说明/提示

【题目来源】

NOIP 2003 普及组第三题

#include <stdio.h>
#define MAX 20
long long ans[MAX][MAX];//二维数组,记得开long long不然可能会溢出
long long dfs(int , int);
int main (void)
{
	int n;
	scanf("%d" , &n);
	printf("%lld\n" , dfs(n , 0));
	return 0;
}
 
long long dfs(int queue , int stack)//第一个参数表示队列里元素,第二个参数表示栈里面的元素
{
	if(ans[queue][stack] != 0)//如果答案还是初始化值,那么不能直接返回
	{
		return ans[queue][stack];
	}
	if(queue == 0)//如果队列里面的元素全部入栈了
	{
		return 1;//就返回这一种情况
	}
	if(stack > 0)//如果栈里面还有元素
	{
		ans[queue][stack] += dfs(queue , stack - 1);//我们可以进行一次出栈操作
	}
	ans[queue][stack] += dfs(queue - 1 , stack + 1);//不出栈,那就入栈
	return ans[queue][stack];//返回答案
}

这个C程序是一个使用深度优先搜索(DFS)和记忆化搜索来计算队列和栈操作序列数量的程序。程序的主要目的是计算在给定初始队列元素数量n的情况下,所有可能的队列和栈操作序列的数量。每个操作要么是从队列中取出一个元素放入栈中,要么是从栈中取出一个元素(如果栈不为空)。

以下是代码中每一步的详细解释:

全局变量和宏定义

c复制代码

#include <stdio.h> 

#define MAX 20

long long ans[MAX][MAX]; // 二维数组,用于记忆化搜索,避免重复计算

  • #include <stdio.h>:包含标准输入输出库,用于scanfprintf等函数。
  • #define MAX 20:定义一个宏,表示二维数组ans的大小。
  • long long ans[MAX][MAX];:定义一个二维数组,用于存储已经计算过的结果,以避免重复计算。

DFS函数声明和主函数

c复制代码

long long dfs(int , int);

int main (void) 

{

int n;

scanf("%d" , &n); // 从标准输入读取队列的初始元素个数

printf("%lld\n" , dfs(n , 0)); // 调用DFS函数并打印结果

return 0;

}

  • long long dfs(int , int);:声明DFS函数,它接受两个整数参数(队列元素数量和栈元素数量),并返回一个长整型结果。
  • scanf("%d" , &n);:从标准输入读取一个整数,表示队列的初始元素个数。
  • printf("%lld\n" , dfs(n , 0));:调用DFS函数计算操作序列数量,并打印结果。

DFS函数实现

c复制代码

long long dfs(int queue , int stack) 

{

if(ans[queue][stack] != 0) // 如果已经计算过这个状态,则直接返回结果

{

return ans[queue][stack];

}

if(queue == 0) // 如果队列为空(即所有元素都已入栈)

{

return 1; // 只有一种情况,即栈中的所有元素依次出栈

}

if(stack > 0) // 如果栈不为空

{

ans[queue][stack] += dfs(queue , stack - 1); // 可以进行一次出栈操作,并继续搜索

}

ans[queue][stack] += dfs(queue - 1 , stack + 1); // 将队列中的一个元素入栈,并继续搜索

return ans[queue][stack]; // 返回答案

}

  • if(ans[queue][stack] != 0):检查是否已经计算过当前状态(队列元素数量和栈元素数量)的结果。如果是,则直接返回结果,避免重复计算。
  • if(queue == 0):如果队列为空(即queue为0),则只有一种情况,即栈中的所有元素依次出栈,返回1。
  • if(stack > 0):如果栈不为空(即stack大于0),则可以进行一次出栈操作,并递归调用DFS函数继续搜索。
  • ans[queue][stack] += dfs(queue - 1 , stack + 1);:无论栈是否为空,都可以将队列中的一个元素入栈,并递归调用DFS函数继续搜索。
  • return ans[queue][stack];:返回答案,并将结果存储在二维数组ans中,以便后续可能的重复利用。

通过记忆化搜索,程序能够避免重复计算相同状态的结果,从而大大提高了效率

这是一个简单的C程序,用于读取一系列整数,直到遇到0为止,然后逆序输出这些整数。现在,我会逐步解释这个程序中的每一行代码。

P1427 小鱼的数字游戏

## 题目描述

小鱼最近被要求参加一个数字游戏,要求它把看到的一串数字 $a_i$(长度不一定,以 $0$ 结束),记住了然后反着念出来(表示结束的数字 $0$ 就不要念出来了)。这对小鱼的那点记忆力来说实在是太难了,你也不想想小鱼的整个脑袋才多大,其中一部分还是好吃的肉!所以请你帮小鱼编程解决这个问题。

## 输入格式

一行内输入一串整数,以 $0$ 结束,以空格间隔。

## 输出格式

一行内倒着输出这一串整数,以空格间隔。

## 样例 #1

### 样例输入 #1

```
3 65 23 5 34 1 30 0
```

### 样例输出 #1

```
30 1 34 5 23 65 3
```

## 提示

#### 数据规模与约定

对于 $100\%$ 的数据,保证 $0 \leq a_i \leq 2^{31} - 1$,数字个数不超过 $100$。

#include<stdio.h>
int a[105];
int main(){
	int n=0,i;
	while(scanf("%d",&a[++n])&&a[n]!=0);
	n--;
	for(i=n;i>=1;i--) printf("%d ",a[i]);
	return 0;
}


 

c复制代码

#include<stdio.h>

这行代码包含了标准输入输出库,使得程序可以使用printfscanf等函数。

c复制代码

int a[105];

这行代码定义了一个整数数组a,其大小为105。这个数组用于存储用户输入的整数。

c复制代码

int main(){

这是主函数的开始。C程序的执行从这里开始。

c复制代码

int n=0,i;

这行代码定义了两个整数变量nin用于记录用户输入了多少个整数(不包括最后的0),初始化为0。i用于后续的循环。

c复制代码

while(scanf("%d",&a[++n])&&a[n]!=0);

这行代码是一个while循环,它的功能是不断读取用户输入的整数,直到遇到0为止。

  • scanf("%d",&a[++n]):这部分代码读取一个整数,并将其存储在数组a的下一个位置(即a[n+1])。同时,n的值会自增1。
  • &&a[n]!=0:这是一个条件判断,检查刚刚读取的整数(现在存储在a[n]中)是否不等于0。如果等于0,循环就会停止。

所以,这个循环会一直读取整数,直到遇到0为止。此时,n的值会指向最后一个非零整数的下一个位置。

c复制代码

n--;

由于n现在指向最后一个非零整数的下一个位置,所以这行代码将n的值减1,使其指向最后一个非零整数。

c复制代码

for(i=n;i>=1;i--) printf("%d ",a[i]);

这行代码是一个for循环,用于逆序输出数组a中的整数。

i=n:初始化in,即最后一个非零整数的索引。

i>=1:循环继续的条件是i大于或等于1。

i--:每次循环迭代后,i的值都会减1。

printf("%d ",a[i]):在循环体内,这行代码输出数组a中索引为i的整数,并在其后添加一个空格。

c复制代码

return 0;

这行代码表示主函数执行完毕,并返回0,表示程序正常结束。

c复制代码

}

这是主函数的结束。

总结:这个程序的主要功能是读取一系列整数(直到遇到0为止),然后逆序输出这些整数。

P1996 约瑟夫问题

# 约瑟夫问题

## 题目描述

$n$ 个人围成一圈,从第一个人开始报数,数到 $m$ 的人出列,再由下一个人重新从 $1$ 开始报数,数到 $m$ 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

**注意:本题和《深入浅出-基础篇》上例题的表述稍有不同。书上表述是给出淘汰 $n-1$ 名小朋友,而该题是全部出圈。**

## 输入格式

输入两个整数 $n,m$。

## 输出格式

输出一行 $n$ 个整数,按顺序输出每个出圈人的编号。

## 样例 #1

### 样例输入 #1
10 3
### 样例输出 #1
3 6 9 2 7 1 8 5 10 4

## 提示

$1 \le m, n \le 100$

#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[105];
int main(){//a[t]如果为真就表示出圈,为假就表示还在圈内 
   cin >> n >> m;
   int t=0,s=0,f=0;//t从头遍历每个数,初始为0;s递增到m就置0
   while(f!=n){//f表示出圈的人数,递增到n就结束 
	  t++;
	  if(t==n+1)//t遍历到了最后+1,此时将其置成1; 
		 t=1;
	  if(a[t]==0)//如果a[t]为假,就让s+1 
		 s++;
	  if(s==m){//s等于m,此时t表示的人应该出圈,然后让s变为0 
		 s=0;
		 a[t]=1;//出圈,让其变为真 
		 cout << t << ' ';//打印,输出 
		 f++;//出圈人数加1 
	  }
   }
   return 0;
}

这个代码模拟了一个常见的游戏:约瑟夫环问题(Josephus problem)。在这个问题中,有n个人围成一圈,从某个人开始报数,数到m的人会被淘汰(出圈),然后从下一个人开始继续报数,直到所有人都被淘汰。

以下是对代码的逐行解释:

  1. #include <bits/stdc++.h>:包含C++的标准库,这是竞赛编程中常用的头文件,因为它几乎包含了所有需要的库。

  2. using namespace std;:使用标准命名空间,这样在后续代码中就不需要为标准库中的函数和对象加std::前缀。

  3. int n,m;:定义两个整数变量nm,分别表示总人数和每次报数的上限。

  4. int a[105];:定义一个大小为105的整数数组a。这个数组用来表示每个人是否还在圈内,0表示在圈内,1表示已经出圈。

  5. int main(){:主函数开始。

  6. cin >> n >> m;:从标准输入读取nm的值。

  7. int t=0,s=0,f=0;:定义三个整数变量:

    • t:当前考虑的人的索引(从1开始)。
    • s:当前已经报的数,初始为0。
    • f:已经出圈的人数,初始为0。
  8. while(f!=n){:当还有人在圈内时,继续循环。

  9. t++;:将t加1,考虑下一个人。

  10. if(t==n+1) t=1;:如果t超出了人数范围(即t变为n+1),则将其重置为1,模拟环状结构。

  11. if(a[t]==0):如果当前人t还在圈内(即a[t]为0),则执行以下操作:

    • s++;:报的数加1。
  12. if(s==m):如果报的数达到了m,则执行以下操作:

    • s=0;:重置报的数为0。
    • a[t]=1;:标记当前人t为出圈。
    • cout << t << ' ';:输出出圈人的索引。
    • f++;:出圈人数加1。
  13. }while循环结束。

  14. return 0;:主函数返回0,表示程序正常结束。

这个代码使用了数组a来模拟每个人是否还在圈内的状态,并使用三个变量tsf来追踪游戏的状态。最终,代码会输出所有出圈人的索引。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值