The Clocks
@题目@
The First,我是在QQ群里见到了王小朋友在无助的求助这道题,于是我就顺便把这道题AC了,还用了DFS与BFS 两种方法 来做,做得很爽【误】
So,在此阐述一下我的一些心得(顺便抚平一下王海粼小朋友内心的伤痕)
@解析@
如果把每次操作用数字表示,并限定每种操作只能做0~3次,那么找到一个由操作组成的数列t1,t1…t2,t2….,t3,t3…t9,t9…使得每一个
那么我们的目的就变成了“寻找一个可重复元素数列a1,a2,a3….an使得它满足判定函数
生成数列对吧,我们最常用的当然是——
@万能的DFS@
void DFS(int k)
{
if(k>9)
{
Check(a);
return ;
}
for(i=0;i<=3;i++)
{
DFS(k+1);
a[++l]=i;
}
for(i=0;i<=3;i++)
a[--l]=0;//回溯
}
非常的简单明了,相信我也不需要做过多解释对吧(被打)
我们递归到第k层就意味着要加入操作t[k],但是加入的个数不一定,于是我们用了一个for循环枚举操作次数0~3次,然后继续加入t[k+1]…最后,当9种操作依次加入完毕,我们就可以进行我们的
那么问题进一步的来了,我们最最关键的
检验函数Check()
首先我们先将时钟的状态存放在某数组clock里面
然后我们将对应操作所要影响的时钟对应存放在一个9*9的矩阵里
Like this:
bool c[15][15]={
{0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,0,0,0,0},
{0,1,1,1,0,0,0,0,0,0},
{0,0,1,1,0,1,1,0,0,0},
{0,1,0,0,1,0,0,1,0,0},
{0,0,1,0,1,1,1,0,1,0},
{0,0,0,1,0,0,1,0,0,1},
{0,0,0,0,1,1,0,1,1,0},
{0,0,0,0,0,0,0,1,1,1},
{0,0,0,0,0,1,1,0,1,1}
};
我们曾在解析中提到,满足条件的数列应该对于任何一个钟i,总有
bool check()
{
for(int i=1;i<=l;i++)
for(int j=1;j<=9;j++)
clock[j]+=c[a[i]][j];
for(int i=1;i<=9;i++)
if(clock[i]%4!=0)return 0;
return 1;
}
那么我们DFS的主要内容就完成了!
(p.s.此段代码笔者偷了个懒,没有亲自测试过是否可靠,要copy的直接copy吧,反正我也不能确保能不能对哈哈哈哈哈哈哈哈哈)
@难搞的BFS@
我们先将一个状态A={0}(即没有做任何操作)存入队首
然后用一个for依次枚举第1~9种操作,将得到的新状态存入队尾
我们仍然可以用在DFS中提到的
但,为什么我要称其为“难搞”的呢?
因为其判重的方法,可能真的是闻所未闻、用也难用的——
散列Hash
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间。
摘自百度百科
- 我们为什么要用Hash
因为我们共有9个钟的状态,如果要把9个钟存放在一个9维数组里的话……呵呵呵呵呵
- Hash应该怎么用
我们定义一个函数Hash(clock),便可以将当前9个时钟状态转化为一个int(或long long)的一个数
- Hash里面应该写些什么
(注意,以下非常重要,请认真做好笔记)
我们现在已知的这个数列Clock,便从这个数列的特殊点入手
即:找到Clock数列与一般性数列的不同
于是我们可以显而易见的发现,Clock数列每个元素的值只能取0,1,2,3
这点并不显然,若将Clock数列的每个元素接在一起,我们可以得到一个9位4进制数(请好好理解,最好琢磨推敲一下为什么)
一个9位的4进制显然不好存储,但我们已经知道它是一个4进制,便可顺势将其转化为10进制整数
这样,我们的Hash函数大概就OK了
(p.s.对Hash最好的理解的例题,便是“八数码问题”,大家可以自行查阅资料)
(p.p.s.如果这一段暂时不懂可以先跳过,等来日再琢磨)
- 代码
const int MN=262144;//4^10
const int f[]={0,1,4,16,64,256,1024,4096,16384,65536};//四进制每一位的单位大小
bool C[MN+5];//判重
...
int Hash(int Clock[])
{
int x=0;
for(int i=1;i<=9;i++)
x+=Clock[i]*f[i];
return x;
}
一些优化
我们的BFS也就这样写得差不多了,这也应该是正确(我依然没有测试哈哈哈)。但,我们仍然不能掉以轻心
虽然空间上基本不能在优化,但在时间上,我们仍然可以进行适当的剪枝,避免搜索到绝对重复的状态。
方法如下所述:
我们生成的序列a1,a2,a3,a4…an,为了保证其不会重复,我们可以采用按序搜索。即:保证a1<=a2<=a3<=…<=an。这样搜索的范围就会进一步的缩小
实现如下:
struct Node{
int Hash;
int Far;
int Oper;
int Max;//每一次延伸的状态>=Max
}FIFO[MN+5];
@小结@
做完这一题,用了几种不同的方法,我感觉我整个灵魂都快升华了……
果然是一题多解啊……
话说最大的收获应该是关于Hash方面的知识,挺令人意外的
【另:附上王小朋友用枚举做了过后写的博客,听说他挺不服的,你们可以自行去观摩一下然后点个赞】