每天一道藍橋杯 Day3 移動字母

liyishui發表於2024-03-09

題意:

思考過程:

首先觀察這道題的資料範圍不是很大,一共才6個位置,並且每個位置只出現一次。

那麼不考慮合法,只算總狀態的話就是7*6*5*4*3*2*1=720

狀態數很少,啟發我們可以用搜尋!

那麼搜尋是用dfs還是bfs?

bfs有一個特性:從s出發,第一次搜尋到狀態t時所用的步數,肯定是所需的最小操作次數。

因為bfs每次擴充套件時只多走一步,假設第一次到某個點所用步數為c,最優解為c'

如果c'<=c,bfs每次會把能擴充套件的都擴充套件掉,則一定會更早擴充套件到c'而不是c。

這個性質有一個直觀的呈現:圖中藍色的是起點,綠色的是終點,紅色的是障礙物,粉色的是最優解。

而dfs則不一定,有可能存在更優的解法,要全部搜尋完才能找到最優解。

再回頭看這道題,由於只詢問是否可達,不問最優解的話,那dfs或者bfs都可以。

用dfs實現:

實現上也有一些小技巧。

1.讀入的是字串,但是實際上移動時又是在一個2*3的字元陣列上向上下左右移動。

直接分位置討論的話由於資料小也是可行的,不過可以考慮引入方向陣列

dx[4]={0,0,-1,1},dy[4]={1,-1,0,0}

//假設當前在的位置是(x,y),擴充時只需要

for(int i=0;i<4;i++){
     int tx=x+dx[i],ty=y+dy[i];  
}

就可以省去分類討論的麻煩。

2.像這種終點明確,起點很多的詢問,如果操作是可逆的,就可以考慮從終點出發,預處理出所有可達的狀態。

具體地,我們可以從"ABCDE*"出發,dfs出所有能到的狀態,把能到的狀態打標記。

處理詢問時,只需要查詢這個狀態有沒有被搜尋過。

程式碼:

#include<bits/stdc++.h>
using namespace std;
int dx[4]={0,0,-1,1};
int dy[4]={-1,1,0,0};
char a[3][7];
map<string,int>mp;
int cnt=0;
string check( ){
    string s="";//把陣列再轉回字串,方便用map儲存
    for(int i=1;i<=2;i++)
     for(int j=1;j<=3;j++){
        s=s+a[i][j];
     }
    return s;
}
void dfs(int x,int y){
    if(mp[check( )]) return;
    mp[check( )]=1;//對當前訪問到的狀態打標記,防止之後又被搜尋,時間複雜度將會很大。
    for(int i=0;i<4;i++){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<=2&&tx>=1&&ty<=3&&ty>=1){//擴充的格子肯定不能越界
            swap(a[tx][ty],a[x][y]);
            dfs(tx,ty);
            swap(a[tx][ty],a[x][y]);//回溯
        } 
    } 
}
void solve(){
    a[1][1]='A';a[1][2]='B';a[1][3]='C';//轉化成二維陣列
    a[2][1]='D';a[2][2]='E';a[2][3]='*';
    dfs(2,3);
}
int main(){
    solve();//預處理出所有終點能到的狀態,只要終點能到,那麼它也可以到終點。
    int t;cin>>t;
    for(int i=1;i<=t;i++){
        if(t==1) cout<<1<<endl;
        else cout<<mp[check()]<<endl;
    }
}

題外話:

1.這道題官方資料出鍋啦,只要輸出1就能過。

2.這道題的背景應該是“八數碼”,“八數碼”是一個求給定狀態到目的狀態的最少操作次數,操作也是移動某個特定格子。