【YbtOj】虫食算

题目链接:http://noip.ybtoj.com.cn/contest/16/problem/3

 

说实话,我觉得这道题无论是题目(标题)还是题目(内容)都很恶心!没有一点扎实的数学基础,没有一点丰富的数学知识,没有一点巧妙的数学技巧根本做不出来!!!

 

Saction A     输入/数据处理 

三行数据,可以用一个字符串数组来存储(只要你弄清楚字符串数组的第一个数据是行,第二个数据表示列),把这三行数据分别存进字符串的每一个组里(每一行),接着输入就结束了。

当然,输入部分最关键的不是输入,而是输入处理!

想要进行dfs,必不可少的就是对目标状态的控制,这里我们要提前考虑到输入的这个数据在搜索中的几个状态:第一,这个数在哪;第二,这个数是否已经被填过。

这个一维的字符串数组可以比拟成一个二维字符数组,但其稳定性是绝对远超字符数组,从右到左,从上到下,我们把这个字符串数组或者说是虫食算式整个遍历一遍,用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即可。

考虑到在搜索的过程,其搜索的深度其实就是已经搜索了的字母的个数,而搜索的对象应该是一个字母,而不是整个虫食算式(废话,我们搜索的目的就是为了枚举每一个字母的值,当然,如果你想开N个for循环的话就是另外一个故事了,不用说这个故事的结局肯定很惨,惨到你连程序都写不出来,除非你特别牛),我们本来可以打一个26字母的跳转表,但因为这个式子的进制是不定的,很容易搜索越界,判断起来更麻烦,所以,我们可以开一个q(queue)数组,按照字母出现的顺序进行储存。

对于这个q数组,它的下标变量(如果你看不懂我在写什么,我建议你仔细阅读以下CCF对一维数组的讲解)可以单独用一个num存储,num从0开始计数,每当计入一个“字母”,就++num,为什么要先加在计入呢?很简单,我们最后的输出是按照字母表的顺序,而不是按照输入的顺序,所以我们想要计入的是这个字母在字母表中的排位,在搜索中作为一个类似指针的工具,详见下。

最后一个重要的问题!为什么初始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 }

 

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

 

posted on 2021-08-06 22:13  百里狂生  阅读(76)  评论(0编辑  收藏  举报

导航