第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,先找到一個,再找下一個,(每個箭頭算一步,因爲光標會且只會移動到該方向下一個不同的字符),紅色是正確走法
在這裏插入圖片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<queue>
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章