谈乱搞

我是个懒人 直接从知乎我的回答下复制过来

https://www.zhihu.com/question/325498088/answer/692904069

谢wyy邀

作为我校第一乱搞选手(雾)我经常乱搞a题或者获得很大部分的分 我认为乱搞水平毫无疑问是OI 综合水平的一部分 我认为乱搞的本质是对题目所具有的特殊性质的极致利用 是题目性质分析能力的一部分

StilllFantasy提到的第一种乱搞是低级的 甚至不是真正意义上的乱搞 真正的乱搞至少应当保证随机数据下的正确性(就算不能保证正确也要保证正确率)并能应对(出题人)易想到的极端数据

下面几条是我对于乱搞的建议

1、既然我们要以正确性为一个目标,对于最优化问题(乱搞的重要应用)在时间允许的情况下,不妨多做几次,或者多跑几种乱搞。乱搞的复合或重复都能让你的正确率大幅增长。你让一个正确性10%的程序跑1000次,或者10个不同方向的50%正确性程序各跑一遍,总的正确性是很可观的。

2、在不要求顺序的情况下,请采用随机顺序。这会带来效率和正确性的大幅提升。

3、不要忽视每一个“常数优化”,它很可能带来复杂度的提升(如小于和小于等于)。

4、在操作的每一步,如果有可以以低于该操作复杂度的时间处理的特殊情况(例如所处理的一组数相等),请特判并处理它。同样,这可能会带来效率和准确性的大幅提升。

 

如果要专门练习乱搞,可以去cc上找challenge题 cc上有大量challenge题,大多数是乱搞题

最后,如果你仅仅想骗分而不是乱搞,不妨考虑出题人会如何去出数据(如什么样的极端数据),针对这个写骗分程序

===分割线===

几个乱搞的例子

1、一般图最大匹配

这是一个可能已经很多人知道的做法。

首先有一个非常容易想到的做法:我从每个尚未匹配的点x出发,找邻居匹配。如果该点y也没匹配就成功了,否则设y匹配了z,暂时使xy互相匹配,从z出发找下一个,如果成功了就成功了,如果没成功就退回来,重新把yz匹配,x找下一个邻居。

显然这个做法t飞了。我们试图优化它。

我们每次从一个尚未匹配的点出发时,都要求每个点只能被访问一遍。这样,时间复杂度变为O(n^3).

显然这个做法wa得厉害。我们试图把它变得对一点。

穷举尚未匹配的点和每个点的出边是没有顺序要求的。我们不妨随机穷举尚未匹配的点的顺序,然后每访问到一个点时随机每个点的出边。(建议2)

注意到每访问到一个点,如果它有未被匹配的邻居可以直接匹配,检测是O(n)的,所以不妨没到一个点,先检测它有没有未被匹配的邻居。(建议4)

最后,因为每次顺序是随机的,我们可以选择把上述做法跑10次。(建议1)

上述做法的正确性非常优秀,而且时间复杂度O(n^3),可以顺利通过。

我在这里第一次见到了这个做法:

http://uoj.ac/submission/283233

这个做法有一定可推广性,比如我在面对pkuwcd2t2的时候,一眼看出了做法,但是不会tarjan(科技树歪了),O(n^2)的暴力又被卡常了,使用了类似这个做法来找环,顺利通过了subtask2.

2、Blogewoosh #6

http://codeforces.com/blog/entry/62602

这可能也是一个很多人知道的做法。

其实我第一次看到这种做法不在这里,在一场模拟赛里,我自己把它做出来了。我下面以Radewoosh的题为例,说说我是怎么想的(肯定没Radewoosh讲得好)。

这里忽略103000的限制,假装它是一道OI题,只要你在O(n)跑出就行。

首先,最朴素的想法是对每个数找出它的值,这样个数O(logn),一共O(nlogn).

考虑到对每个数判定是否会对当前的max产生贡献是O(1)的,远小于O(logn),我们不妨做它。(建议4)

我们发现顺序没有影响,不妨采用随机顺序。(建议2)

注意判定是否会对当前的max产生贡献时请用是否严格大于max而不是大于等于max。这句话看起来很蠢但是我第一次用这个方法的时候差点在这里犯错。如果你用大于等于max就会被卡回O(nlogn).(建议3)

此时做法可以通过此题了。时间复杂度Radewoosh已给出。

注意这里不需要把它做几遍,因为这里保证了正确性是100%,不需要多次跑这个程序。

这个做法也有可推广性,对于n个等可能成为答案而且 判断它的值的复杂度 大于 判断它的值与某个数的大小的复杂度 时都能用。

 

 

上面2个是优美而又有推广性的乱搞。下面再给出一些丑陋且没什么推广性的乱搞,让我们看看乱搞如何应对不那么适合乱搞的题目。

 

 

3、tc7487 ConstructPolyline

从最简单的说起。

题面现在坏了找不到了,有点尴尬。我先凭着记忆说一说。

有50个三维向量,有的加有的减(必须用),使它们的结果的模长最大。

考虑一个一个的加入向量。我们相信最终模长最大的向量一直是在前250000大的,所以每次把当前的所有结果向量(至多250000个)变成至多500000个的全部结果向量,然后取前250000个。

大概这个做法比较优,所有我当时不仅没有用随机顺序,甚至用的不是nth_element而是sort(整整多出一个log),就ac了。

4、tc12197 XorAndSum

有50个大小1e15的数,你可以做任意多次操作:使a变成a^b.求最后所有数的和的最大值。

这题我用的乱搞实在太丑陋了,就不说怎么想的了。直接给出解法。

做20000次。每次顺着或倒着扫,然后把它变成它的前缀能表示出的最大数。如果没变或者每50次就random_shuffle一次。每200次做一次对答案的更新,更新方法如下:挑出一组基底,把对别的数用它们能表示的最大数做贡献,对基底用所有大于等于它的基底的xor做贡献。

因为讲不清楚这个乱搞,贴个代码。

因为当时写得比较匆忙,代码更丑。

https://pastebin.ubuntu.com/p/579nGq7bKw/

 

 

 

下面的乱搞并不能ac,但能取得较高的分数,用以说明用乱搞取得部分分的情况

 

 

5、cc CLOSTEST Closest Points

得分呢:0.948

题意是有50000个1类点和50000个2类点(3维)。对每个2类点找出和它最近的1类点。每找对一个得到一定分数。

我们考虑一个朴素的想法:用kdt。它t飞了。我们想到一个优化:只让kdt跑233层就退出来。这样,它不t了,但是得的分不是很可观。

我们考虑另一个朴素的想法:我们暴力的找其中1000个的答案,其它不管。这个做法得分惨淡。我们注意到,靠近的点可能会是相同的答案。我们sort以后每50个点找一下答案,其它的用之前那个关键点当答案。分数有小许提升。

我们用想法2优化想法1。sort以后每45个点暴力找一次,其它点用前一个点的答案做初值跑kdt,跑233层就返回。这样,我们得到了0.948的高分。

https://www.codechef.com/viewsolution/22950183

6、

我忘了我那个oj的账号密码了

先咕着 下周再更

 

总结:进行乱搞的时候,我们先观察题目性质找到1个或几个朴素的解法,然后把他们融合起来,然后用4条建议进行优化,就能得到正确性和时间复杂度都有保障的解法。这应当是OI综合水平的一部分。

==============2020.7.30更新==============

高考回来了

在做题过程中有了新的感悟

多次重复一个随机过程的时候可以考虑改变某些随机相关的常数,而不是把完全相同的过程重复多次,让随机过程经过的情况覆盖面更广一点。

我在打多校的时候 现场在做Head Maker的时候采用了这个想法并ac

http://acm.hdu.edu.cn/showproblem.php?pid=6809

https://paste.ubuntu.com/p/GbFFRb5RNb/

但是我比赛结束以后试了一下发现好像不加这个也能过 但是可能在有些题上需要这个优化

(遗憾的是,我第一个开的就是这个题,一下就想到这个做法,但是没敢写,终场前才写,与一血(3h08min)失之交臂)

本场比赛的另一题也是比较有趣的随机算法

http://acm.hdu.edu.cn/showproblem.php?pid=6804

把2班的w变成相反数,然后做dp。如果random_shuffle一下,可以想象用到的值域不会太大.于是我们选择一个不太大的值域并做多次来保证正确性就好了。

我现场采用的是[-40000,40000]值域,跑3次 大约是0.98的极限情况单次成功率。

https://paste.ubuntu.com/p/D2TtXVfKhk/

出题人则采用了4sqrt(2n)*w的值域使单次成功率达到0.9999999

这告诉我们 有的时候高成功率做单次比低成功率做多次优 需要进行计算(或者模拟)来权衡

原文地址:https://www.cnblogs.com/skylineidolon/p/13405706.html