usaco教程

USACO教程

Complete Search 枚举搜索
                         
思想:


写枚举搜索时应遵循KISS原则(Keep it simple stupid,译为写最单纯愚蠢的程序,意思是应把程序写得尽量简洁),竞赛时写程序的最终目标就是在限制时间内求出解,而不需太在意否还有更快的算法。

枚举搜索具有强大的力量,他用直接面向答案并尝试所有方案的方法发现答案。这种算法几乎总是解题时你第一个想到的方法。如果它能在规定的时间与空间限制内找出解,那么它常常很容易编写与调试。这就意味着你可以有时间去解答其他难题,即那些不能显示枚举算法强大的题目。


如果你面对一道可能状态小于两百万的题目,那么你可以考虑使用枚举搜索。尝试所有的状态,看看它们是否可行。

小心!小心!


有时,题目不会直接要求你使用枚举算法。


例题1:派对灯
[IOI 98]

在一次IOI派对上有N个灯和4个灯开光,第一个开关可以使所有灯改变状态(关上开着的灯,开启关着的灯),第二个开关可以改变所有偶数位上灯的状态,第三个开关可以改变所有奇数位上灯的状态,第四个开关控制着灯14710……3n+1)。


告诉你N的值和所有按开关的次数(小于10,000次)。并告诉你某些灯的状态(例如:7号灯是关着的,10号灯是开着的)请编程输出所有灯可能的最后状态。


很明显,每次按开关你都要偿试4种可能。那么总共要试 410000 次(大约 106020),那意味着你没有足够的时间去使用枚举搜索,但是我们将枚举方法改进一下,那就可以使用枚举了。因为无论有多少个灯,由于开关控制的特殊性,都会出现6个灯一次循环的情况,即1号灯的状态永远与7号灯,13号灯,19号灯……相同,2号灯的状态也永远与8号灯,14号灯,20号灯……相同。同样,无论你按了多少次开关,按同一个开关两次就相当于没有按该开关,那么每一个开关就只需要考虑按一次或没有按,那么这题的枚举量就很小了。


例题2: 时钟调整
[IOI 94]

有九个钟被摆放在一个3 X 3的矩阵中,它们各自指向1200900600300中的一种,你的目的是将它们的指针全部调向1200。很遗憾,每一次调整你都只能从九种调整方案中选择一种执行(九种方案已被从19编号),每一种方案可以改变固定钟的状态(例如:方案1控制钟123,方案2控制钟147,方案3控制钟5689……),即将方案指定的所有钟向前拨快3小时(使时针向顺时针方向旋转90度),请你输出一个数列,使得按该数列表示方案执行后,所有钟都指向1200。并且如果把整个序列看作一个数,要求该数最小。


最容易想到的方法是用递归枚举19的方案在该步使用。很可怕,由于递归的层数在此没有限定,所以将用掉 9k 的时间(k为层数),那可能是相当巨大的。其实,不用紧张,细心的你一定会发现:当一个方案执行4次后,就相当于没有执行,又因为题目要求输出最优解,那么任意一个方案都没有必要4次以上执行。也就是说,只需要枚举每一个方案的4种情况(没执行,执行123次)就可以了。他仅有49 ,约262,072此枚举,我们的计算机1s内就可以算完,应该算是极快了。


类似问题:


挤牛奶 [USACO 1996 初赛
]

给出一个挤牛奶的顺序(农夫 A 300 秒到 1000秒时挤牛奶, 农夫 B 700 秒到 1200秒),要求输出:


最长的有人挤牛奶的时间。


最长的没人挤牛奶的时间。


完全数牛与完全数牛群 [USACO 1995 决赛
]

如果一个数可以由它的某几个约数相加得到,那么我们叫它完全数,如28 = 1 + 2 + 4 + 7 + 14。而一对完全对数就是指两个数都可以由对方的约数相加得出。同样一个完全数组就是一个数组的第一个数可以由第二个数的约数加和得到,第二个数也可以由第三个数的约数相加得到……最后一个数可以由第一个数的约数加和得到。


现在Farmer John已经将它的牛儿们编好了号(132000)请找出其中所有的完全数牛与完全数牛群。

贪心算法


样例:牛棚修理 [1999 USACO 春季公开赛]

Farmer John
有一列牛棚,在一次暴风中,牛棚的一整面墙都被吹倒了,但还好不是每一间牛棚都有牛。Farmer John 决定卖木料来修理牛棚,然而,刻薄的木材提供商却只能提供有限块的木料(木料的长度不限),现在告诉你关着牛的牛棚号,和提供的木材个数N,你的任务是编程求出最小的木块长度和。(1 <= N <= 50

贪心思想:

贪心思想的本质是每次都形成局部最优解,换一种方法说,就是每次都处理出一个最好的方案。例如:在样例中,若已经发现 N = 5 时的最优解,那么我们可以直接利用 N = 5 的最优解构成 N = 4 的最优解,而不用去考虑那些 N = 4 时的其他非最优解。

贪心算法的最大特点就是快。通常,二次方级的存储要浪费额外的空间,而且很不幸,那些空间经常得不出正解。但是,当使用贪心算法时,这些空间可以帮助算法更容易实现且更快执行。

贪心的难点:

贪心算法有两大难点:

如何贪心:

怎样才能从众多可行解中找到最优解呢?其实,大部分都是有规律的。在样例中,贪心就有很明显的规律。但你得到了 N = 5 时的最优解后,你只需要在已用上的5块木板中寻找最靠近的两块,然后贴上中间的几个牛棚,使两块木板变成一块。这样生成的 N = 4 的解必定最优。因为这样木板的浪费最少。同样,其他的贪心题也会有这样的性质。正因为贪心有如此性质,它才能比其他算法要快。

贪心的正确性:

要证明贪心性质的正确性,才是贪心算法的真正挑战,因为并不是每次局部最优解都会与整体最优解之间有联系,往往靠贪心生成的解不是最优解。这样,贪心性质的证明就成了贪心算法正确的关键。一个你想出的贪心性质也许是错的,即使它在大部分数据中都是可行的,你必须考虑到所有可能出现的特殊情况,并证明你的贪心性质在这些特殊情况中仍然正确。这样经过千锤百炼的性质才能构成一个正确的贪心。

在样例中,我们的贪心性质是正确的。如下:

假设我们的答案盖住了较大的空牛棚连续列,而不是较小的。那么我们把那部分盖空牛棚的木板锯下来,用来把较小的空牛棚连续列盖住,还会有剩余。那么锯掉它们!还给木材商!同时我们的解也变小了。也就是说,我们获得更优的解。所以,靠盖住较大空牛棚连续列的方法无法获得最优解,我们也应该尽量贪心那些距离小的木板合并。

如果仍有一个空牛棚连续列与我们的答案盖住的那个相同,我们同样使用上述的方法。会发现获得的新解与原解相同,那么不论我们选哪个,结果都将一样。

由此可见,如果我们合并的两块木板间距离最短,那么总能获得最优解。所以,在解题的每一步中,我们都只需要寻找两块距离最小的木板并合并它们。这样,我们获得的解必定最优。

结论:

如果有贪心性质存在,那么一定要采用!因为它容易编写,容易调试,速度极快,并且节约空间。几乎可以说,它是所有算法中最好的。但是应该注意,别陷入证明不正确贪心性质的泥塘中无法自拔,因为贪心算法的适用范围并不大,而且有一部分极难证明,若是没有把握,最好还是不要冒险,因为还有其他算法会比它要保险。

类似问题:

三值排序问题 [IOI 1996]

有一个由N个数值均为123的数构成的序列(N<= 1000),其值无序,现要求你用最少的交换次数将序列按升序顺序排列。

算法:排序后的序列分为三个部分:排序后应存储1的部分,排序后应存储2的部分和排序后应存储3的部分,贪心排序法应交换尽量多的交换后位置正确的(21)、(31)和(32)数对。当这些数对交换完毕后,再交换进行两次交换后位置正确的(123)三个数。

分析:很明显,每一次交换都可以改变两个数的位置,若经过一次交换以后,两个数的位置都由错误变为了正确,那么它必定最优。同时我们还可发现,经过两次交换后,我们可以随意改变3个数的位置。那么如果存在三个数恰好为123,且位置都是错误的,那么进行两次交换使它们位置正确也必定最优。有由于该题具有最优子结构性质,我们的贪心算法成立。

货币系统 -- 一个反例 [已删节]

奶牛王国刚刚独立,王国中的奶牛们要求设立一个货币系统,使得这个货币系统最好。现在告诉你一个货币系统所包含的货币面额种类(假设全为硬币)以及所需要找的钱的大小,请给出用该货币系统找出该钱数,并且要求硬币数尽量少。


算法:每次都选择面额不超过剩余钱数但却最大的一枚硬币。例如:有货币系统为{12510},要求找出16,那么第一次找出10,第二次找出5,第三次找出1,恰好为最优解。

错误分析: 其实可以发现,这种算法并不是每一次都能构成最优解。反例如:货币系统{15810},同样找16,贪心的结果是1051三枚,但用两枚8的硬币才是最优解。因为这样,贪心的性质不成立,如此解题也是错的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值