P1044 [NOIP2003 普及组] 栈
栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。
栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。
栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。
题目描述
宁宁考虑的是这样一个问题:一个操作数序列,1,2,…,�1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 �n。
现在可以进行两种操作,
- 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
- 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 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>:包含标准输入输出库,用于scanf和printf等函数。
- #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> |
这行代码包含了标准输入输出库,使得程序可以使用printf和scanf等函数。
c复制代码
int a[105]; |
这行代码定义了一个整数数组a,其大小为105。这个数组用于存储用户输入的整数。
c复制代码
int main(){ |
这是主函数的开始。C程序的执行从这里开始。
c复制代码
int n=0,i; |
这行代码定义了两个整数变量n和i。n用于记录用户输入了多少个整数(不包括最后的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:初始化i为n,即最后一个非零整数的索引。
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
的人会被淘汰(出圈),然后从下一个人开始继续报数,直到所有人都被淘汰。
以下是对代码的逐行解释:
-
#include <bits/stdc++.h>
:包含C++的标准库,这是竞赛编程中常用的头文件,因为它几乎包含了所有需要的库。 -
using namespace std;
:使用标准命名空间,这样在后续代码中就不需要为标准库中的函数和对象加std::
前缀。 -
int n,m;
:定义两个整数变量n
和m
,分别表示总人数和每次报数的上限。 -
int a[105];
:定义一个大小为105的整数数组a
。这个数组用来表示每个人是否还在圈内,0表示在圈内,1表示已经出圈。 -
int main(){
:主函数开始。 -
cin >> n >> m;
:从标准输入读取n
和m
的值。 -
int t=0,s=0,f=0;
:定义三个整数变量:t
:当前考虑的人的索引(从1开始)。s
:当前已经报的数,初始为0。f
:已经出圈的人数,初始为0。
-
while(f!=n){
:当还有人在圈内时,继续循环。 -
t++;
:将t
加1,考虑下一个人。 -
if(t==n+1) t=1;
:如果t
超出了人数范围(即t
变为n+1
),则将其重置为1,模拟环状结构。 -
if(a[t]==0)
:如果当前人t
还在圈内(即a[t]
为0),则执行以下操作:s++;
:报的数加1。
-
if(s==m)
:如果报的数达到了m
,则执行以下操作:s=0;
:重置报的数为0。a[t]=1;
:标记当前人t
为出圈。cout << t << ' ';
:输出出圈人的索引。f++;
:出圈人数加1。
-
}
:while
循环结束。 -
return 0;
:主函数返回0,表示程序正常结束。
这个代码使用了数组a
来模拟每个人是否还在圈内的状态,并使用三个变量t
、s
和f
来追踪游戏的状态。最终,代码会输出所有出圈人的索引。