【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 }

 

嗯,逻辑很复杂,但它就是对的,事情每每就是这样奇怪!

相关文章
相关标签/搜索