目录
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 <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 说明
其实很多时候,我们是不用刻意关心字符的精读这个问题的
只有在题目要求需要读取空格等特殊字符时我们再进一步考虑