【IOI1994】The Clocks

The Clocks


@题目@


这里写图片描述

The First,我是在QQ群里见到了王小朋友在无助的求助这道题,于是我就顺便把这道题AC了,还用了DFS与BFS 两种方法 来做,做得很爽【误】
So,在此阐述一下我的一些心得(顺便抚平一下王海粼小朋友内心的伤痕


@解析@

如果把每次操作用数字表示,并限定每种操作只能做0~3次,那么找到一个由操作组成的数列t1,t1…t2,t2….,t3,t3…t9,t9…使得每一个 Clock[i]0(mod4) ,我们就可以认为这是一个答案
那么我们的目的就变成了“寻找一个可重复元素数列a1,a2,a3….an使得它满足判定函数 Check(a) ,并且n最小”
生成数列对吧,我们最常用的当然是——


@万能的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() 函数
那么问题进一步的来了,我们最最关键的 Check() 函数又该如何去写呢?

检验函数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,总有

Clock[i]0(mod4)
所以函数应该这么写

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中提到的 Check() 函数来检验答案的正确性,但不同的是,我们不必比较数列的长短(因为BFS产生的数列一定是由短到长的)
但,为什么我要称其为“难搞”的呢?
因为其判重的方法,可能真的是闻所未闻、用也难用的——

散列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方面的知识,挺令人意外的
【另:附上王小朋友用枚举做了过后写的博客,听说他挺不服的,你们可以自行去观摩一下然后点个赞

The End

猜你喜欢

转载自blog.csdn.net/tiw_air_op1721/article/details/72582164