算法竞赛入门经典——数据结构基础

算法竞赛入门经典——数据结构基础

再谈栈和队列

问题:铁轨(UVa 514)

某城市有一个火车站,有n节车厢从A方向驶入车站,按进站顺序编号为1~n。你的任务是判断是否能让它们按照某种特定的顺序进入B方向的铁轨并驶出车站。例如,出栈顺序(5 4 1 2 3)是不可能的,但(5 4 3 2 1)是可能的。
为了重组车厢,你可以借助中转站C。这是一个可以停放任意多节车厢的车站,但由于末端封顶,驶入C的车厢必须按照相反的顺序驶出C。对于每节车厢,一旦从A移入C,就不能再回到A了;一旦从C移入B,就不能回到C了。换句话说,在任意时刻,只有两种选择:A->C和C->B。

分析

在中转站C中,车厢符合后进先出的原则,因此是一个栈。

#include<cstdio>
#include<stack>
using namespace std;
const int MAXN = 1000 + 10;
int n,target[MAXN];
int main()
{
	while(scanf("%d",&n)==1){
		stack<int> s;
		int A=1,B=1;
		for(int i=1;i<=n;i++){
			scanf("%d",&target[i]);
		}
		int ok=1;
		while(B<=n){
			if(A==target[B]){
				A++;
				B++;
			}
			else if(!s.empty()&&s.top()==target[B]){
				s.pop();
				B++;
			}
			else if(A<=n){
				s.push(A++);
			}
			else{
				ok=0;
				break;
			}
		}
		printf("%s\n",ok?"Yes":"No");
	}
	return 0;
} 

链表

问题:破损的键盘(UVa 11988)

你有一个破损的键盘。键盘上的所有键都可以正常工作,但有时Home键或者End键会自动按下。你并不知道键盘存在这一问题,而是专心地打稿子,甚至连显示器都没打开。当你打开显示器之后,展现在你面前的是一段悲剧的文本。你的任务是在打开显示器之前计算出这段悲剧文本。
输入包含多组数据。每组数据占一行,包含不超过100000个字母、下划线、字符“[”或者“]”。其中字符“[”表示Home键,“]”表示End键。输入结束标志为文件结束符(EOF)。输入文件不超过5MB。对于每组数据,输出一行,即屏幕上的悲剧文本。

样例输入:

This_is_a_[Beiju]_text

[[]][][]Happy_Birthday_to_Tsinghua_University

样例输出:

BeijuThis_is_a__text

Happy_Birthday_to_Tsinghua_University

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int const maxn=100000+5;
int last,cur,next[maxn];//光标位于cur号字符的后面
char s[maxn];
int main()
{
        while(scanf("%s",s+1)==1)
        {
                int n=strlen(s+1);//输入保存在s[1],s[2]...中
                last=cur=0;
                next[0]=0;
                for(int i=1;i<=n;i++)
                        {
                                char ch=s[i];
                                if(ch=='[') cur=0;
                                else if(ch==']') cur=last;
                                else //插入
                                {
                                        next[i]=next[cur]; //现在插入的字符的指向的下一个位置(next)变成了cur指向的下一个位置(next)
                                        next[cur]=i; //而cur指向的下一个位置就指向了要插入字符的位置(i),这样就完成了插入,与上一句位置不能颠倒
                                        if(cur==last) last=i;//更新“最后一个字符”编号
                                        cur=i; //移动光标
                                }
                        }
                for(int i=next[0];i!=0;i=next[i])
                        printf("%c",s[i]);
                printf("\n");
        }
        return 0;
}

数和二叉树

二叉树的编号

问题:小球下落(UVa 679)

有一棵二叉树,最大深度为D,且所有叶子的深度都相同。所有结点从上到下从左到右编号为1,2,3,…,2的D次方-1。在结点1处放一个小球,它会往下落。每个内结点上都有一个开关,初始全部关闭,当每次有小球落到一个开关上时,状态都会改变。当小球到达一个内结点时,如果该结点上的开关关闭,则往左走,否则往右走,直到走到叶子结点。
一些小球从结点1处依次开始下落,最后一个小球将会落到哪里呢?输入叶子深度D和小球个数I,输出第I个小球最后所在的叶子编号。假设I不超过整棵树的叶子个数。D<=20。输入最多包含1000组数据。

样例输入:

4 2
3 4
10 1
2 2
8 128
16 12345

样例输出:

12
7
512
3
255
36358

分析

不难发现,对于一个结点k,其左子结点、右子结点的编号分别为2k和2k+1.这个结论非常重要,请读者引起重视。

#include<cstdio>
#include<cstring>
const int maxd = 20;
int s[1<<maxd];
int main()
{
	int D,I;
	while(scanf("%d%d",&D,&I)==2){
		memset(s,0,sizeof(s));
		int k,n=(1<<D)-1;
		for(int i=0;i<I;i++){
			k=1;
			for( ; ; ){
				s[k]=!s[k];
				k=s[k]?k*2:k*2+1;
				if(k>n){
					break;
				}
			}
		}
		printf("%d\n",k/2);
	}
	return 0;
}

每个小球都会落在根结点上,因此前两个小球必然是一个在左子树,一个在右子树。一般地,只需看小球编号的奇偶性,就能知道它是最终在哪颗子树中。对于那些落入根结点左子树的小球来说,只需知道该小球是第几个落在根的左子树里的,就可以知道它下一步往左还是往右了。依此类推,直到小球落到叶子上。
如果使用题目中给出的编号I,则当I是奇数时,它是往左走的第(I+1)/2个小球;当I是偶数时,它是往右走的第I/2个小球。这样,可以直接模拟最后一个小球的路线:

#include<cstdio>
#include<cstring>
const int maxd = 20;
int s[1<<maxd];
int main()
{
	int D,I;
	while(scanf("%d%d",&D,&I)==2){
		int k=1;
		for(int i=0;i<D-1;i++){
			if(I%2){
				k=k*2;
				I=(I+1)/2;
			}
			else{
				k=k*2+1;
				I/=2;
			}
		}
		printf("%d\n",k);
	}
	return 0;
}

用DFS求连通块

问题:油田(UVa 572)

输入一个m行n列的字符矩阵,统计字符“@”组成多少个八连块。如果两个字符“@”所在的格子相邻(横、竖或者对角线方向),就说它们属于同一个八连块。

分析

和前面的二叉树遍历类似,图也有DFS和BFS遍历。由于DFS更容易编写,一般用DFS找连通块:从每个“@”格子出发,递归遍历它周围的“@”格子。每次访问一个格子时就给它写上一个“连通分量编号”(即下面代码中的idx数组),这样就可以在访问之前检查它是否已经有了编号,从而避免同一个格子访问多次。

#include<cstdio>
#include<cstring>
const int maxn = 100 + 5;
char pic[maxn][maxn];
int m,n,idx[maxn][maxn];
void dfs(int r,int c,int id)
{
	if(r<0||r>=m||c<0||c>=n){
		return ;
	}
	if(idx[r][c]>0||pic[r][c]!='@'){
		return ;
	}
	idx[r][c]=id;
	for(int dr=-1;dr<=1;dr++){
		for(int dc=-1;dc<=1;dc++){
			if(dr!=0||dc!=0){
				dfs(r+dr,c+dc,id);
			}
		}
	}
}
int main()
{
	while(scanf("%d%d",&m,&n)==2&&m&&n){
		for(int i=0;i<m;i++){
			scanf("%s",pic[i]);
		}
		memset(idx,0,sizeof(idx));
		int cnt=0;
		for(int i=0;i<m;i++){
			for(int j=0;j<n;j++){
				if(idx[i][j]==0&&pic[i][j]=='@'){
					dfs(i,j,++cnt);
				}
			}
		}
		printf("%d\n",cnt);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值