【Keyboarding】 题解
【思路分析】
首先用字符串数组把键盘和要打的字母读入,一定要在要求的字符串最后加个“\(*\)”。 (感受一下本蒟蒻爆零的痛苦)
之后便开始我们愉快的搜索过程了:先计算出所要的字符串的长度,再寻找每一位上与答案字符串相符的字符,统计出到达每个字符的按键次数,取搜索成功的最小值即为答案。
到了这个时候便有问题了,因为键盘很大,要求我们打的字符串长度最多有10000,而且有很多对我们没有用的字符干扰,这样枚举每一个能到到达的位置必然会完美的 \(TLE\)。
这就要求我们思考了,如何能够减少无用的信息呢?
其实我们可以对键盘做一个预处理,寻找在该方向上能够到达的第一个不同的字符,如果无解(超出键盘还没有找到),就把它附一个很大的数,表示找不到。
可以用两个三维数组来存从当前字符是否能到到下个字符;
如下面的 \(disx[k][i][j]\) 和 \(disy[k][i][j]\) 。
其中的第一维 \([k]\) 表示向哪个方向移动,第二维 \([i]\),第三维 \([j]\) 表示坐标。
【Code】
void init()
{
for(int i = 1; i <= n; ++i) {//枚举行;
for(int j = 1; j <= m; ++j) {//枚举列;
for(int k = 1; k <= 4; ++k) {//枚举每个方向;
int sx = i, sy = j;
while(mp[sx][sy] == mp[i][j]){
//当寻找到的字符和原来的字符相同时,就继续枚举;
sx += dx[k];
sy += dy[k];
}
if(check(sx,sy)) {
//待会在完整代码里展示出,判断是否出了边界;
disx[k][i][j] = sx;
disy[k][i][j] = sy;
//如果没出边界,就附上从这个字符能到的下个字符的坐标;
} else {
disx[k][i][j] = INF;
disy[k][i][j] = INF;//否则 附为 0x3f3f3f3f;
}
}
}
}
return;
}
之后就开始我们最终的搜索了,因为前面的预处理,使得减少了很多冗杂的信息。
这个时候再设置一个三维数组表示到 \((x,y)\) 的已有的字符串长度的按键次数,开始先设置为 $0x3f3f3f3f $。设置一个结构体来表示到现在的坐标 \((x,y)\) 的按键次数和字符串长度;
如果新找到的字符串的长度的按键次数比原有的字符串的长度的按键次数少,就更新按键次数,使得再排除一些冗杂信息。(貌似有点绕口?),不过我相信你们能看懂QAQ。
易错点
- 在队列搜索中 要设定两个值 \(now\) 和 \(cur\) ,因为在每次放入新值时都会把 \(now\) 给替换为别的值。我第一次就是因为这种情况导致一直调不出来。
接下来就上代码了,一些解释我会写在代码里。
【Code】
#include <cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<stdlib.h>
#include<time.h>
#include<map>
#include<vector>
#include<set>
#define ull unsigned long long
#define ll long long
#define M 1000010
#define N 1010
#define qaq cout<<"可行QAQ"<<endl
#define INF 0x3f3f3f3f
using namespace std;
const int mod1 = 19260817;
const int mod2 = 19660813;
/*================================================*/
int n,m,len;
char mp[55][55];//键盘
char yuan[10010];//要求的字符串
int dx[5] = {0, 1, -1, 0, 0};
int dy[5] = {0, 0, 0, 1, -1};//方向
int disx[5][55][55],disy[5][55][55];//能到达的位置的坐标
struct node {
int x , y , cnt , len ;
} now , cur ;//从左到右依次为 : 横坐标,纵坐标,按键次数,字符串长度
int length[10010][55][55];//储存按键次数
queue<node> qp;
/*================================================*/
bool check(int x,int y) //判断是否出边界
{
if(x < 1 || x > n) return false;
if(y < 1 || y > m) return false;
return true;
}
void init()
{
for(int i = 1; i <= n; ++i) {//枚举行;
for(int j = 1; j <= m; ++j) {//枚举列;
for(int k = 1; k <= 4; ++k) {//枚举每个方向;
int sx = i, sy = j;
while(mp[sx][sy] == mp[i][j]){
//当寻找到的字符和原来的字符相同时,就继续枚举;
sx += dx[k];
sy += dy[k];
}
if(check(sx,sy)) {
disx[k][i][j] = sx;
disy[k][i][j] = sy;
//如果没出边界,就附上从这个字符能到的下个字符的坐标;
} else {
disx[k][i][j] = INF;
disy[k][i][j] = INF;//否则 附为 0x3f3f3f3f;
}
}
}
}
return;
}
int bfs()
{
memset(length,INF,sizeof(length));
now.x = 1; now.y = 1; now.len = 0; now.cnt = 0;
//初始化一开始的位置,按键次数,找到的字符串长度。
length[0][now.x][now.y] = 0;//当找到的字符串长度为 0 是按键次数为 0
qp.push((node){ now.x, now.y, now.cnt, now.len});//压入队列
while(qp.size()) {
cur = qp.front();//cur 是 光标的意思
qp.pop();
while(yuan[cur.len + 1] == mp[cur.x][cur.y]) {//寻找匹配的字符
cur.len++;
cur.cnt++;
}
if(cur.len == len) return cur.cnt;
//退出条件 :把要求的字符串打完了
for(int i=1;i<=4;i++) {//向 4 个方向枚举
now.x = cur.x; now.y = cur.y;
now.len = cur.len; now.cnt = cur.cnt;
//因为 下面 now.x 和 now.y 被附为了新值。
//所以这里每一遍都要把 now = cur.
if(disx[i][now.x][now.y] != INF ||
disy[i][now.x][now.y] != INF) {//如果有可到达的字符
now.x = disx[i][cur.x][cur.y];
now.y = disy[i][cur.x][cur.y];
now.len = cur.len;
now.cnt = cur.cnt + 1;//赋值
if(now.cnt < length[now.len][now.x][now.y]) {//如果小于
length[now.len][now.x][now.y] = now.cnt;
//更新到现在长度的最小值。
qp.push((node){now.x , now.y , now.cnt , now.len});
}
}
}
}
}
/*=================================================*/
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%d%d",&n,&m);
for(int i = 1 ;i <= n; ++i){
scanf("%s",mp[i] + 1);
}
scanf("%s",yuan + 1);//读入
yuan[strlen(yuan + 1)+ 1] = '*';//最后一定要加 * ; !!!!
len = strlen(yuan + 1);//计算要求的字符串长度
init();
int ans = bfs();
printf("%d",ans);//输出
return 0;
}

浙公网安备 33010602011771号