题目连接: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 }
嗯,逻辑很复杂,但它就是对的,事情每每就是这样奇怪!