草庐IT

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

菜只因C 2025-05-20 原文

目录

1  例题

1.1  卡片换位

1.2  人物相关性分析

2  字符串的读取

2.1  综述

2.2  scanf

2.3  getline/getchar/get

2.4  注意

2.5  说明

3  C语言中字符串有关问题

3.1  常用函数

3.2  使用实例

3.3  附一些函数


先看例题

1  例题

1.1  卡片换位

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

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

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

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

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

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

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

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

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

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

代码(附讲解):

#include <bits/stdc++.h>
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 <bits/stdc++.h>
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

    vector<ll>Alice,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<<ans;
    return 0;
}

遍历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<<str<<endl;

2.3  getline/getchar/get

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

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

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

使用string

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

方法二:

使用字符串数组

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这种函数最常见的应用就是吃字符

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

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

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

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

2.5  说明

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

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

3  C语言中字符串有关问题

3.1  常用函数

strlen(字符串名):测定字符串长度

gets(字符串名):读取字符串

strcat(字符串1,字符串2):将字符串2接在字符串1后面

strncat(字符串1,字符串2,最多拼接字母数):将字符串2接在字符串1后面,最多拼接字符数有限制

strcpy(字符串1,字符串2):把字符串2拷贝给字符串1

strncpy(字符串1,字符串2,最多拷贝字符串):把字符串2拷贝给字符串1,最多拼接字符数有限制

3.2  使用实例

例子:字符串中单词数量(两种写法)

#include <bits/stdc++.h>
using namespace std;
char str[100];
int i=0,cnt=0;

int main()
{
	gets(str);
	/*if(str[0]>='a'&&str[0]<='z'||str[0]>='A'&&str[0]<='Z') cnt++; 
	for(i=1;i<strlen(str)-1;i++)
	{
		if(str[i]==' ')
		{
			if(str[i+1]>='a'&&str[i+1]<='z'||str[i+1]>='A'&&str[i+1]<='Z') cnt++; 
		}
	}*/
	if(isalpha(str[0])) cnt++; 
	for(i=1;i<strlen(str)-1;i++)
	{
		if(str[i]==' ')
		{
			if(isalpha(str[i+1])) cnt++; 
		}
	}
	printf("%d",cnt);
	return 0;
} 

例子:字符串拼接

#include <stdio.h>
#include <string.h>
char a[100],b[100];

int main()
{
	gets(a);
	gets(b);
	strcat(a,b);
	puts(a);
	strncat(a,b,10);
	puts(a);
	return 0; 
}

例子:字符串比较函数返回值

#include <stdio.h>
#include <string.h>
char a[100],b[100];

int main()
{
	gets(a);
	gets(b);
	printf("%d ",strcmp(a,b));
	printf("%d",strcmp(b,a));
	return 0;
}

例子:比较三个字符串的大小

#include <stdio.h>
#include <string.h>
char str1[100],str2[100],str3[100],str[100];
int main()
{
	gets(str1);
	gets(str2);
	gets(str3);
	if(strcmp(str1,str2)>0) strcpy(str,str1);//如果字符串1大于字符串2,则暂存字符串1 
	else strcpy(str,str2);//否则,暂存字符串2 
	if(strcmp(str3,str)>0) strcpy(str,str3);//如果字符串大于暂存,3是最大的 
	printf("%s",str);//否则,string是最大的 
	return 0;
}

3.3  附一些函数

1.判断是否是字母

isalpha(),若是返回true,否则返回false

2.判断是否是数字

isdigit(),若是返回true,否则返回false

3.判断是否是大写字母

isupper(),  若是返回true,否则返回false;

4.判断是否是小写字母

islower(),若是返回true,否则返回false;

5.大写转化为小写字母

tolower()

6.小写转化为大写字母

toupper()
 

有关字符串的特殊读取——基于蓝桥杯两道题目(C/C++)的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  9. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

随机推荐