第1部分 基础算法(提高篇)--第4章 广搜的优化技巧1452:Keyboarding

1452:Keyboarding

时间限制: 1000 ms 内存限制: 65536 KB
提交数: 433 通过数: 153
【题目描述】
出自 World Final 2015 F. Keyboarding

给定一个 r 行 c 列的在电视上的“虚拟键盘”,通过「上,下,左,右,选择」共 5 个控制键,你可以移动电视屏幕上的光标来打印文本。一开始,光标在键盘的左上角,每次按方向键,光标总是跳到下一个在该方向上与当前位置不同的字符,若不存在则不移动。每次按选择键,则将光标所在位置的字符打印出来。

现在求打印给定文本(要在结尾打印换行符)的最少按键次数。

【输入】
第一行输入 r,c。

接下来给出一个 r×c 的键盘,包括大写字母,数字,横线以及星号(星号代表 Enter 换行)。

最后一行是要打印的文本串 S,S 的长度不超过 10000。

【输出】
输出打印文本(包括结尾换行符)的最少按键次数。保证一定有解。

【输入样例】
2 19
ABCDEFGHIJKLMNOPQZY
X*****************Y
AZAZ
【输出样例】
19
【提示】
样例输入2:

5 20
12233445566778899000
QQWWEERRTTYYUUIIOOPP
-AASSDDFFGGHHJJKKLL*
–ZZXXCCVVBBNNMM–**


ACM-ICPC-WORLD-FINALS-2015
样例输出2:

160
样例输入3:

6 4
AXYB
BBBB
KLMB
OPQB
DEFB
GHI*
AB
样例输出 3
7
数据范围:

对于 100% 的数据,1≤r,c≤50, S 的长度不超过 10000。


思路:样例:假设我们要打印"AB"
1.多组数据
2.如果某方向上没有不同字符光标不会动
我们每次预处理出每个点向四个方向下次到达的点。然后bfs即可
注意bfs每次只能扩展一层(也就是说距离dis每次最多只能+1,否则无法保证最优性)!
处理字符用map
蓝色框是一个一个字符去BFS,先找到一个,再找下一个,(每个箭头算一步,因为光标会且只会移动到该方向下一个不同的字符),红色是正确走法
第1部分 基础算法(提高篇)--第4章 广搜的优化技巧1452:Keyboarding_第1张图片

#include
#include
#include
#include
#include
#include
#include
using namespace std;
map <char,int> Map;//用map把字符转成数字
int n,m,ans;
int mp[57][57],xx[4]={0,1,0,-1},yy[4]={1,0,-1,0};//mp存键盘状态
int nex[57][57][4][2];//nex存每个点向4个方向走,走到的点的横坐标和纵坐标
inline void premap()//预处理map
{
    for(int i=1;i<=10;i++) Map[(char)('0'+i-1)]=i;
    for(int i=0;i<=25;i++) Map[(char)('A'+i)]=i+11;
    Map['-']=37; Map['*']=38;
}
inline void prenex()//预处理nex
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<4;k++)
            {
                int x=i+xx[k],y=j+yy[k];
                while(mp[i][j]==mp[x][y]) x+=xx[k],y+=yy[k];//如果相同就继续走
                nex[i][j][k][0]=x; nex[i][j][k][1]=y;
            }
}
struct node
{
    int x,y,stp,dis;
    //x,y存横纵坐标,stp存当前已经选了几个字符,dis表示进行了几次操作
};//广搜的队列里每个点的内容
int lst[10007],vis[57][57],len;
//lst存给定的字符串转成数字后的情况
//vis是记忆化数组,存走到点 i j 时选择的字符最多为多少
inline void bfs()
{
    queue <node> q;//每次都重新开一个队列,如果重复用也可以,但是要注意清空
    q.push( (node){1,1,0,0} );//把初始状态压入队列
    while(!q.empty())
    {
        node u=q.front(); q.pop();
        if(mp[u.x][u.y]==lst[u.stp])//如果当前光标所在的字符是当前想要的字符
        {
            if(u.stp==len)//如果是最后一个字符,就代表找到了最少步数
            {
                ans=u.dis+1;//注意+1
                return;
            }
            //否则
            u.stp++; u.dis++;//选择此字符
            vis[u.x][u.y]=u.stp;//更新vis
            q.push(u);//重新加入队列
        }
        else//如果不是想要的字符
        {
            int x,y;
            for(int k=0;k<4;k++)//尝试向4个方向移动
            {
                x=nex[u.x][u.y][k][0]; y=nex[u.x][u.y][k][1];
                if(x<1||x>n||y<1||y>m) continue;//判断越界
                if(vis[x][y]>=u.stp) continue;//剪枝
                vis[x][y]=u.stp;
                q.push( (node){x,y,u.stp,u.dis+1} );//新状态加入队列
            }
        }
    }
}
char ch[10007];
int main()
{
    premap();
    while(scanf("%d",&n)!=EOF)
    {
        memset(nex,0,sizeof(nex));
        memset(mp,0,sizeof(mp));
        memset(lst,0,sizeof(lst));
        memset(vis,-1,sizeof(vis));//注意初始化
        ans=0;
        scanf("%d",&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",ch);
            for(int j=0;j<m;j++)
                mp[i][j+1]=Map[ch[j]];//读入键盘并转成数字
        }
        scanf("%s",ch); len=strlen(ch);
        for(int i=0;i<len;i++) lst[i]=Map[ch[i]];//读入字符串并转成数字
        lst[len]=38;//最后要加一个'*'号
        prenex();
        bfs();
        cout<<ans<<endl;
    }
    return 0;
}

你可能感兴趣的:(信息学C++,一本通)