【YbtOj】蟲食算

題目連接: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 }

 

嗯,邏輯很複雜,但它就是對的,事情每每就是這樣奇怪!

相關文章
相關標籤/搜索