題目連接:http://noip.ybtoj.com.cn/contest/16/problem/3c++
說實話,我以爲這道題不管是題目(標題)仍是題目(內容)都很噁心!沒有一點扎實的數學基礎,沒有一點豐富的數學知識,沒有一點巧妙的數學技巧根本作不出來!!!數組
Saction A 輸入/數據處理 框架
三行數據,能夠用一個字符串數組來存儲(只要你弄清楚字符串數組的第一個數據是行,第二個數據表示列),把這三行數據分別存進字符串的每個組裏(每一行),接着輸入就結束了。函數
固然,輸入部分最關鍵的不是輸入,而是輸入處理!工具
想要進行dfs,必不可少的就是對目標狀態的控制,這裏咱們要提早考慮到輸入的這個數據在搜索中的幾個狀態:第一,這個數在哪;第二,這個數是否已經被填過。spa
這個一維的字符串數組能夠比擬成一個二維字符數組,但其穩定性是絕對遠超字符數組,從右到左,從上到下,咱們把這個字符串數組或者說是蟲食算式整個遍歷一遍,用v(visited)來記數字是否已被使用。在這個遍歷的過程當中,咱們能夠將這兩個數組初始化,注意這裏的數組裏的每一項包含的是一類字母的值和狀態,而不是蟲食算式裏的每一個字母,那樣太麻煩了。指針
在v數組中咱們定義:0表示沒有被用(能夠用),1表示已被用(不能用填)。如今,咱們將這個v數組借用一下,用它來存「這個字母是否已被遍歷」,在初始化時置設定爲1。因爲須要一一對應(「11對應」也許更直觀)的初始化,因此咱們必須找到s[][]所表明的字母在字母表中的位置。由於是從右到左,從上到下,因此在循環變量爲 i 和 j 的循環中s[j][i]能夠用來表示指向的字符。根據ascll碼,s[j][i]-'A'+1表示s[j][i]中的字母在字母表中排第s[j][i]-'A'+1位,所以只要讓v[s[j][i]-'A'+1]裏的值變爲1便可。code
考慮到在搜索的過程,其搜索的深度其實就是已經搜索了的字母的個數,而搜索的對象應該是一個字母,而不是整個蟲食算式(廢話,咱們搜索的目的就是爲了枚舉每個字母的值,固然,若是你想開N個for循環的話就是另一個故事了,不用說這個故事的結局確定很慘,慘到你連程序都寫不出來,除非你特別牛),咱們原本能夠打一個26字母的跳轉表,但由於這個式子的進制是不定的,很容易搜索越界,判斷起來更麻煩,因此,咱們能夠開一個q(queue)數組,按照字母出現的順序進行儲存。對象
對於這個q數組,它的下標變量(若是你看不懂我在寫什麼,我建議你仔細閱讀如下CCF對一維數組的講解)能夠單獨用一個num存儲,num從0開始計數,每當計入一個「字母」,就++num,爲何要先加在計入呢?很簡單,咱們最後的輸出是按照字母表的順序,而不是按照輸入的順序,因此咱們想要計入的是這個字母在字母表中的排位,在搜索中做爲一個相似指針的工具,詳見下。blog
最後一個重要的問題!爲何初始v數組要初始爲1?不是應該初始爲0嗎?沒錯咱們的確應該將v數組所有初始爲0,可是這樣就出現了一個問題,咱們但願q數組裏面紀錄一遍進入的字母就好了,那麼重複的字母怎麼記?重複的字母固然要被屏蔽掉,最好的方法就是在計入字母順序的時候同事記錄其是否已經入隊,這個時候就能夠借用v數組,在循環中加一個if判斷該字母是否已經入隊,聰明如你,你確定知道怎麼寫,A部分代碼以下:
1 int main() 2 { 3 cin>>n; 4 cin>>s[1]>>s[2]>>s[3]; 5 for(int i=n-1;i>=0;i--) 6 for(int j=1;j<=3;j++) 7 if(!v[s[j][i]-'A'+1]) 8 { 9 v[s[j][i]-'A'+1]=1; 10 q[++num]=s[j][i]-'A'+1; 11 } 12 memset(v,0,sizeof(v)); 13 memset(ans,-1,sizeof(ans)); 14 dfs(1); 15 return 0; 16 }
Saction B 搜索
搜索的框架很簡單。
首先說一下終止條件。當搜索的深度大於進制數N(說白了就是把全部的字母搜了個遍),就return,但吸收了數毒「數獨遊戲」的經驗,咱們裏一個flag,當終止條件成立時旗幟倒下,在dfs中第一個判斷旗幟,若是倒下,馬上退出。
在終止條件到達時,咱們能夠輸出答案,開一個for循環便可。
說完終止條件,咱們再來看遞歸和回溯。咱們定義搜索深度爲x,在初始化裏面,咱們將v數組所有定義成了0,表示能夠填。這裏的v數組很巧妙。在某種意義上來講,它是一個空數組, 由於其中輸入字母的順序不定,因此v數組每一項的意義都是不定的,然而當它和q數組結合在一塊兒時,就變成了一個重要的判斷條件。if(!v[i])用來判斷這個字母是否已經被填,接着就是遞歸和回溯的核心。
如今就要來談談q數組的奇妙轉化!咱們定義存儲每一個字母表明的數字的數組爲ans,按照搜索的順序來講應該是核q數組裏面的相吻合,可是,答案的輸出是要按字母表順序的(字典序,嗯,這樣更專業),可是若是把這個q數組中的值做爲ans的下表變量的話,就不一樣了!當搜索深度爲x時,q[x]表示正在搜索(枚舉)在字母表裏第q[x]的字母所表明的的數字,是一個順序,將這個q[x]放在ans裏面變成ans[q[x]],就變成了第q[x]個字母所表明的數字,是一個真實的數字!這個妙用不是用言語可以說清楚的!
到此,一旦找到沒有被用過的數據,就能夠繼續遞歸,但在遞歸以前要作一個check,判斷此數是否合法,這個在Saction C講,B部分代碼以下:
1 void dfs(int x) 2 { 3 if(h) 4 return; 5 if(x>n) 6 { 7 for(int i=1;i<=n;i++) 8 cout<<ans[i]<<" "; 9 h=1; 10 return; 11 } 12 for(int i=0;i<n;i++) 13 { 14 if(!v[i]) 15 { 16 v[i]=1,ans[q[x]]=i; 17 if(check()) 18 dfs(x+1); 19 v[i]=0,ans[q[x]]=-1; 20 } 21 } 22 }
Saction C check函數
做爲本題核心中的核心,天然是一點都不能忽略!
所謂數據合法,無非就是看這個數據代入蟲食算式是否成立。那麼考慮數學功底的時刻到了!
無論三七二十一,咱們先把目前的算式導出來:開一個for循環,從右往左,一次取出從上到下的三個數,手動運算!咱們定義x,y,z分別表示加數,被加數,和(是sum不是and)。接下來,咱們有須要調用到s字符串數組了,聰明如你,我就直接上代碼了:
1 int x=ans[s[1][i-1]-'A'+1],y=ans[s[2][i-1]-'A'+1],z=ans[s[3][i-1]-'A'+1];
取出這三個值,首先應該判斷,這三個字母是否是都已經變成了數字。在初始化中,咱們將ans裏的值所有定義成了-1,此刻只用一次判斷就能夠了。接着咱們就要考慮,如何判斷合法。離開整個蟲食算式,咱們單獨看一列,不難想到高精加。在高精加中最主要的就是進位。對!進位就是這個判斷的關鍵!
咱們定義一個變量t,定義當t爲-1時表示進位不肯定,t爲其餘數值時,表示進位肯定,且進位爲t。
那麼當t不等於-1時,x+y+t確定要等於z才成立考慮到這是一個N進制數,因此要寫(x+y+t)%n==z才行。同時,若是這一列在最左邊,可是居然產生了進位,那確定錯了,這個判斷比較巧妙:i是遞減的(看一看s是怎麼存的就知道爲何是遞減的了),因此當i==1時,才遍歷到了最左邊。若是有進位,那麼x,y,t的和處以進制必定是1。是1啊!1表示true,這麼說直接用if(i==1 && (x+y+t)/n)判斷就能夠了!固然,事實的確如此!
第二種狀況,t等於-1,這個狀態下的進位是不定的,此時這個進位多是0,也多是1,那麼用一個if同時判斷x,y的和加上0與1是否合法就好了,若是兩個都非法,那就確定沒戲了。固然若是i==1,也須要判斷是否能產生進位,不過這個只用看(x+y)/n就好了。
完整代碼以下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 string s[5]; 4 int n,ans[30],v[30],q[30],num,h; 5 bool check() 6 { 7 int t=0; 8 for(int i=n;i;i--) 9 { 10 int x=ans[s[1][i-1]-'A'+1],y=ans[s[2][i-1]-'A'+1],z=ans[s[3][i-1]-'A'+1]; 11 if(x!=-1 && y!=-1 && z!=-1) 12 { 13 if(t!=-1) 14 { 15 if((x+y+t)%n!=z) 16 return 0; 17 if(i==1 && (x+y+t)/n) 18 return 0; 19 t=(x+y+t)/n; 20 } 21 else 22 { 23 if((x+y)%n!=z && (x+y+1)%n!=z) 24 return 0; 25 if(i==1 && (x+y)/n) 26 return 0; 27 } 28 } 29 else 30 t=-1; 31 } 32 return 1; 33 } 34 void dfs(int x) 35 { 36 if(h) 37 return; 38 if(x>n) 39 { 40 for(int i=1;i<=n;i++) 41 cout<<ans[i]<<" "; 42 h=1; 43 return; 44 } 45 for(int i=0;i<n;i++) 46 { 47 if(!v[i]) 48 { 49 v[i]=1,ans[q[x]]=i; 50 if(check()) 51 dfs(x+1); 52 v[i]=0,ans[q[x]]=-1; 53 } 54 } 55 } 56 int main() 57 { 58 cin>>n; 59 cin>>s[1]>>s[2]>>s[3]; 60 for(int i=n-1;i>=0;i--) 61 for(int j=1;j<=3;j++) 62 if(!v[s[j][i]-'A'+1]) 63 { 64 v[s[j][i]-'A'+1]=1; 65 q[++num]=s[j][i]-'A'+1; 66 } 67 memset(v,0,sizeof(v)); 68 memset(ans,-1,sizeof(ans)); 69 dfs(1); 70 return 0; 71 }
嗯,邏輯很複雜,但它就是對的,事情每每就是這樣奇怪!