1. 程式人生 > >dfs 成語接龍

dfs 成語接龍

題目描述

單詞接龍是一個與我們經常玩的成語接龍相類似的遊戲,現在我們已知一組單詞,且給定一個開頭的字母,要求出以這個字母開頭的最長的“龍”(每個單詞都最多在“龍”中出現兩次),在兩個單詞相連時,其重合部分合為一部分,例如 beastbeast和astonishastonish,如果接成一條龍則變為beastonishbeastonish,另外相鄰的兩部分不能存在包含關係,例如atat 和 atideatide 間不能相連。

輸入輸出格式

輸入格式:

 

輸入的第一行為一個單獨的整數nn (n \le 20n≤20)表示單詞數,以下nn 行每行有一個單詞,輸入的最後一行為一個單個字元,表示“龍”開頭的字母。你可以假定以此字母開頭的“龍”一定存在.

 

輸出格式:

 

只需輸出以此字母開頭的最長的“龍”的長度

 

輸入輸出樣例

輸入樣例#1: 複製

5
at
touch
cheat
choose
tact
a

輸出樣例#1: 複製

23

 

  • 兩個單詞合併時,合併部分取的是最小重疊部分(並不是要把相同的全合併)

  • 相鄰的兩部分不能存在包含關係就是說如果存在包含關係,就不能標記為使用過。

  • 每個單詞最多出現兩次.

(其實也就是讀題問題。這些都是我所犯的錯誤,希望大家能注意一下)

好了。然後是解題思路。

首先是預處理,用yc[i][j]來儲存 第i個單詞 後連線 第j個單詞 的 最小重疊部分(mt函式)

後來預處理完了之後就是深搜:

先從第一個到最後一個單詞看一看哪個單詞是指定字母為開頭的,作為深搜的第一個單詞,同時標記使用過一次(vis[i]++)

然後繼續搜吧。

#include<cstdio>
#include<iostream>
#include<string>
#include<cmath> 
using namespace std;
int n;//單詞數 
string tr[30];//儲存字串 
int yc[30][30];//兩個字母的最小重疊部分 
int vis[30];//判斷單詞使用頻率. 
int mt(int x, int y){//mt函式,返回x單詞後連線一個y單詞的最小重疊部分 
    bool pp=true; 
    int ky=0;
    for(int k=tr[x].size()-1;k>=0;k--){//從x單詞尾部向前看看最小重疊部分是從哪裡開始的,以為因為是倒著來,所以保證是最小的 
        for(int kx=k;kx<tr[x].size();kx++){/ 
            if(tr[x][kx]!=tr[y][ky++]){
                pp=false;
                break;
            }
        }
        if(pp==true){//如果說當前以k為開頭的前一個單詞字尾 ,是後面單詞的字首,就馬上返回重疊部分。(tr[x].size()-k是找出來的規律)
            return tr[x].size()-k;        } 
        ky=0;
        pp=true;//不行就繼續
    }
    return 0;
}//可能這裡有點難理解。可以手動模擬一下
char ch;//開頭字母 
int ans=-1;//答案 
int an=0;//每次搜到的當前最長串 
void dfs(int p){//p為尾部單詞編號(p的字尾就是“龍”的字尾,因為p已經連線到”龍“後面了)
    bool jx=false; 
    for(int j=1;j<=n;j++){
        if(vis[j]>=2) continue;//使用了兩次就跳過 
        if(yc[p][j]==0) continue;//兩單詞之間沒有重合部分就跳過 
        if(yc[p][j]==tr[p].size() || yc[p][j]==tr[j].size()) continue;//兩者存在包含關係就跳過 
        an+=tr[j].size()-yc[p][j];//兩單詞合併再減去最小重合部分 
        vis[j]++;//使用了一次
        jx=true;//標記一下當前已經成功匹配到一個可以連線的部分 
        dfs(j); //接上去
        an-=tr[j].size()-yc[p][j];//回溯,就要再減回去那一部分長度 
        vis[j]--;//回溯,使用-- 
    }
    if(jx==false){//jx==false說明不能再找到任何一個單詞可以相連了 
        ans=max(ans,an);//更新ans 
    }
    return;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        cin>>tr[i];
    cin>>ch; 
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            yc[i][j]=mt(i,j); 
        }
    }//預處理yc陣列。yc[i][j]就表示,i單詞後連線一個j單詞的最小重疊部分 
    //比如 i表示at,j表示att. yc[i][j]就為2 但是yc[j][i]就為0.
    //預處理是一個關鍵

    for(int i=1;i<=n;i++){//從頭到尾看一下有沒有以指定開頭字母為開頭的單詞 
        if(tr[i][0]==ch){//如果有,就以當前單詞為基準進行搜尋。 
            vis[i]++;//使用過一次 
            an=tr[i].size();//更新當前串長度 
            dfs(i);//接上
            vis[i]=0;//消除影響 
        } 
    } 
    printf("%d",ans);
    return 0;
}