字符串的特殊读取——基于蓝桥杯两道题目(C/C++)

目录

1  例题

1.1  卡片换位

1.2  人物相关性分析

2  字符串的读取

2.1  综述

2.2  scanf

2.3  getline/getchar/get

2.4  注意

2.5  说明


先看例题

1  例题

1.1  卡片换位

问题描述
你玩过华容道的游戏吗?
这是个类似的,但更简单的游戏。
看下面 3 x 2 的格子
在其中放5张牌,其中A代表关羽,B代表张飞,* 代表士兵。
还有一个格子是空着的。
你可以把一张牌移动到相邻的空格中去(对角不算相邻)。
游戏的目标是:关羽和张飞交换位置,其它的牌随便在哪里都可以。

输入格式:
输入两行6个字符表示当前的局面

输出格式:
一个整数,表示最少多少步,才能把AB换位(其它牌位置随意)
 

这道题第一眼是很难想到是一道搜索题,原因在于没有对空格进行一个正确的理解:

我们知道一个人物可以挪到空格去,但我们可以这样想,

空格也可以代表一个人物(这里将他命名为K),这个人物的特殊之处在于他可以与周围的任何一个人交换位置。

那么我们在最开始的时候就记录几个特殊点的坐标:A关羽,B张飞,K空格,就能够设置搜索的边界了

那么进行搜索时总要有一个开始点,不妨我们选择让空格主动出击,主动去和周围的人物交换,进行上下左右的尝试

每dfs一次就是对华容道阵容的一次更新

重点思路就是这样,下面看一下代码的具体实现

代码(附讲解):

#include 
using namespace std;
int Min=INT_MAX;//设置成最大值
struct
{
	int x,y;	
}a,b,k;//结构体定义A,B,空格坐标
int vis[3][3][3][3][3][3];//记忆化搜索
int Next[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//上下左右走

void dfs(int x1,int y1,int x2,int y2,int x,int y,int step)
{
	if(step>Min) return;//边界1,走不出去了
	if(x1==b.x&&y1==b.y&&x2==a.x&&y2==a.y)//边界2,交换完成
	{
		Min=min(step,Min);
		return;
	}
	if(x<0||x>1||y<0||y>2) return;//边界3,走出迷宫边界
	if(vis[x1][y1][x2][y2][x][y]==1) return;//边界4,已经搜索过这种情况了
	vis[x1][y1][x2][y2][x][y]=1;//开始dfs主干部分
	for(int i=0;i<4;i++)//四个方向搜索
	{
		int tx=x+Next[i][0];
		int ty=y+Next[i][1];
		if(tx==x1&&ty==y1)//空格与A交换位置
		{
			dfs(x,y,x2,y2,x1,y1,step+1);
		} 
		else if(tx==x2&&ty==y2)//空格与B交换位置
		{
			dfs(x1,y1,x,y,x2,y2,step+1);
		} 
		else//空格与*交换位置
		{
			dfs(x1,y1,x2,y2,tx,ty,step+1); 
		}
	}
	vis[x1][y1][x2][y2][x][y]=0;//回溯 
} 
 
int main()
{
 	for(int i=0;i<2;i++)
 	{
 		for(int j=0;j<3;j++)
 		{
 			char s=getchar();//字符串读取后续讲解1
			if(s=='A') 
			{
				a.x=i;
				a.y=j;
			 } 
			if(s=='B')
			{
				b.x=i;
				b.y=j;
			}
			if(s==' ')
			{
				k.x=i;
				k.y=j;
			}
		}
		getchar();//字符串读取后续讲解2
	}
	dfs(a.x,a.y,b.x,b.y,k.x,k.y,0);
	printf("%d",Min);
	return 0;
}

1.2  人物相关性分析

时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分
【问题描述】
小明正在分析一本小说中的人物相关性。他想知道在小说中 Alice 和 Bob 有多少次同时出现。 更准确的说,小明定义 Alice 和 Bob“同时出现”的意思是:在小说文本 中 Alice 和 Bob 之间不超过 K 个字符。 例如以下文本: ThisisastoryaboutAliceandBob.AlicewantstosendaprivatemessagetoBob. 假设 K = 20,则 Alice 和 Bob 同时出现了 2 次,分别是”Alice and Bob” 和”Bob. Alice”。前者 Alice 和 Bob 之间有 5 个字符,后者有 2 个字符。 注意: 1. Alice 和 Bob 是大小写敏感的,alice 或 bob 等并不计算在内。 2. Alice 和 Bob 应为单独的单词,前后可以有标点符号和空格,但是不能 有字母。例如 Bobbi 並不算出现了 Bob。
【输入格式】
第一行包含一个整数 K。 第二行包含一行字符串,只包含大小写字母、标点符号和空格。长度不超 过 1000000。
【输出格式】
输出一个整数,表示 Alice 和 Bob 同时出现的次数。
 

这道题主要考察对细节的处理和对字符串的读取。

首先需要读取这个字符串,关于这个字符串的读取放到后面讲解

然后就是要计算出符合条件的Alice和Bob所在的位置,注意这里的符合条件是指Alice和Bob后面一个字符不是字母(根据常识前面一位不可能是字母)

最后遍历Alice数组,采用双指针法维护Bob动态窗口(反过来也可),找出符合条件的区间,记录答案

#include 
typedef long long ll;
using namespace std;

int main() 
{
    ios::sync_with_stdio(false);
    int k;
    string s;
    ll ans=0;
    cin >> k;
    cin.get();//字符串读取后续讲解3 
    getline(cin, s);//字符串读取后续讲解4

    vectorAlice,Bob;
    for (ll i = 0; i < s.size();) 
    {
        if (s.substr(i, 5) == "Alice") 
        {
            if (i - 1 >= 0 && isalpha(s[i - 1]) || i + 5 < s.size() && isalpha(s[i + 5]))//不满足条件的情况 字符串读取后续讲解5
                continue;
            Alice.push_back(i);
          
            i += 5;
        } 
        else if (s.substr(i, 3) == "Bob") 
        {
            if (i - 1 >= 0 && isalpha(s[i - 1]) || i + 3 < s.size() && isalpha(s[i + 3]))//不满足条件的情况
                continue;
            Bob.push_back(i);
            
            i += 3;
        } 
        else i++;
    }
    for(ll i = 0,l=0,r=0; i < Alice.size(); i++)
    {  //遍历Alice数组元素 
        while(l < Bob.size() && Bob[l] <  Alice[i] - k - 3)    l++;   //维护窗口左边界 
        while(r < Bob.size() && Bob[r] < Alice[i] + k + 5)   r++;   //维护窗口右边界 
        ans += r - l;             //答案加上窗口中元素个数 
    }
    cout<

遍历Alice数组那一部分-k-3和+k+5的原因是:

我们记录的Alice和Bob的位置指的是记录A和B的位置(即首字母的位置),如果Alice在Bob前面,Alice和Bob之间空了k个字符,那么A和B之间空了(k+5)个字符。同理,如果Alice在Bob前面,Alice和Bob之间空了k个字符,那么A和B之间空了(k+3)个字符。

另外,left指针遇到符合条件的值就不再移动,所以left指针最终指向的是不符合条件的值。

right指针遇到不符合条件的值就不再移动,所以right指针最终指向的是符合条件的值

窗口的左边界是不可取的值,窗口的右边界是可取的值,即符合条件的范围是(left,right],所以ans计算方式如上

2  字符串的读取

2.1  综述

由上面两道题,我们可以发现学好字符串的读取和原理还是很有必要的

字符串或字符的输入有好多个函数,scanf、getline()、cin.getline()、cin.get()、gets()、getchar()等

先得出大致结论:

如果输入是不带空格的字符串,那用什么都可以了,建议用scanf或cin(最好用scanf,因为scanf的读取比cin快,同理printf的读取比cout快)

如果输入带空格,那scanf、cin就用不了了,可以考虑以下方法:
如果想用string类型的话,就用getline(cin, s)
如果想用字符数组类型的话,可以用**cin.getline()、cin.get()、gets(),**这里不是特别建议用gets(),因为这个函数可能会出点奇怪的问题,也不建议用cin.get(), 因为这个函数既不能忽略缓冲区的换行符,还不能在输入后抹掉自己的结束符,建议使用cin.getline()
如果输入的时候是单个字符,或者只想要字符串的第一个字符,就可以用getchar()、cin.get()
下面进行具体的讲解:

2.2  scanf

scanf用于字符串数组,遇到空格或换行就停止

char str[10];
scanf("%s", str);
cout<

2.3  getline/getchar/get

可以接受空格,读取时遇到换行符结束

getline有两种读取方法,法一是string流,法二是iostream流,是两个不一样的函数

方法一:(这里还是建议用方法一)

使用string

string str;
getline(cin, str);
cout<

方法二:

使用字符串数组

char str[100];
cin.getline(str, 20, '\n')
//cin.getline()里面三个参数,第一个是要储存的字符串数组,第二个是最大长度 + 1,最后一个位置用来存储'\0',也就是说你填20,但是只能存前19个字符,第三个是结束符,可省略,默认是换行符

getchar一次只能读取一个字符

2.4  注意


程序的输入都建有一个缓冲区,每次输入的过程,其实是计算机先将输入的数据存在输入缓冲区中,而我们使用的输入函数是直接从缓冲区中读取数据。使用cin的时候,遇到空格和换行符和tab键就会停止,此时缓冲区就会有残留数据:空格、换行符、tab。cin和cin.get()从缓冲区读入的时候会自动忽略第一个空格、换行符、tab,就可以正常读入字符串,但有些函数并不能,比如 getline,gets,get, 他们就会直接读取缓冲区剩余的空格、换行符、tab,就导致需要输入的输不进去。

所以如果你使用getline()或gets()或者cin.get()之前用了cin或cin.get(),请务必在cin后面加个getchar();
概括一下:

scanf和cin这类输入的方法不能记录空格、换行、tab的信息,所以这两种输入方法对空格、换行、tab很敏感,他们一遇到空格、换行、tab就只能中止读取(在一开头就空格、换行、tab直接无视),并且空格、换行、tab的字符并不能从缓冲区带走,导致缓冲区有残余,如果这个时候再用get一类的函数,就会出问题

举例:

string s;

cin>>s;

假设我们输入了abcdefg,为了结束输入,我们换行

此时计算机认为我们输入了abcdefg\n,然后abcdefg离开缓冲区,\n残留在缓冲区,如果下一步继续用get等函数,就会直接读取\n,出现错误

get这类函数对空格、换行、tab不敏感,把空格、换行、tab同样当做字符读取,在换行之后,同样地\n留在缓冲区,再用读取函数容易导致错误

所以get这种函数最常见的应用就是吃字符

字符串的特殊读取——基于蓝桥杯两道题目(C/C++)_第1张图片

 比如例题2的读取华容道,我们在输入每一个信息之后都会有空格或者换行代表输入的中止,为了消除这个中止字符对后续读取的影响,我们要加一个getchar()或者getline()把中止字符吃掉

如果你看懂了前面的说明,那么这个时候你就可以知道:

用cin/scanf之后不能盲目用getchar/getline/get,如果要用,就要在cin后吃字符

用getchar/getline/get之后可以用cin/scanf

2.5  说明

其实很多时候,我们是不用刻意关心字符的精读这个问题的

只有在题目要求需要读取空格等特殊字符时我们再进一步考虑

你可能感兴趣的:(蓝桥杯,字符串读取,C/C++,算法,深度优先)