闲话
今天模拟赛。额,怎么说呢?也许算是没有发挥好,不过又貌似发挥的不错(忽然发现自己在说废话)
嗯,这道题,考场上博主凭借强大的数感和惊人的勇气通过 可爱的Charlotte算法 拿下了70分(其实所谓的什么算法就是一阵乱搞,等会你就知道了)
然后后来看题解,woc,区间DP?我想到了啊?不过不是会爆Time Limit的吗?(本萌新感到十二分的不解)
于是去与 Rank1的全宇宙最帅的聚聚聚聚聚聚佬 XXZH 学长交流了一下这一道题
发现——尼玛,原来还可以预处理优化!!!……
好吧,闲言先叙至此,下面附上本题题面——
题面
小凯在上次的疑惑过后又有疑惑了。
小凯定义了一种运算名为“小凯运算”的运算符$
并规定:a$b=((a&b)+(a||b))>>1
为了练习,小凯决定在黑板上写下n个数,并做n-1次“小凯操作”
小凯操作即为:选定两个相邻的数a和b,擦掉他们,然后用a$b替换掉它们
那么,问题来了——最终剩下的那个数可能是多少呢?
小凯当然会做这一题,不过他想拿来考考你
于是请你升序输出所有的可能值
输入格式
第一行一个数代表n
第二行n个数代表初始黑板上的数
输出格式
一行k个整数,表示所有的可能值(用一个空格隔开)
样例输入
4
1 4 3 2
样例输出
1 2
数据范围
对于30%的数据,n<=10;
对于全部的数据,n<=150且黑板上的数均为[0, 7]内的整数。
分析&题解
好的,让我们来分析一下这道鬼畜的好题。
乍一看,数据很水,可以接受O(n^3)甚至更劣的算法。
而且黑板上数的范围也是贼小。
然而仔细一想,发觉纯暴力的时间复杂度却是指数级的(不过仍然秒杀30%的数据啦)……
那么怎么优化这个算法呢?
显然可以想到区间DP(然而我在考场上码出来了一个效率及其玄学的低的DP算法)……
哦,不过突然又想起来一件事:似乎还没有讲a$b表示的是什么。
不过相信各位是那么的聚,一定可以想到这表示的就是a与b的平均数
证明很简单,只要考虑每一个二进制位即可,可以发现a+b与(a&b)+(a||b)是等价的。
于是就证毕了。
回到刚才的话题,咳咳。那么,现在我来讲一讲如何具体实现这个DP吧。
首先,不进过预处理优化有一种很显然的想法,就是保留区间[i, j]的状态f[i, j](用一个bool数组维护0~7分别是否有可能即可)
然后定义两个区间状态取和为f[a]+f[b]
那么我们有状态转移方程
f[i, j] = Σi<=k<j f[i, k]+f[k+1, j];
每多拓展一位显然是一趟O(n^2)
这个序列共有n位
考虑合并的复杂度是O(8^2)的
综上,这个算法的时间复杂度为O(64 * n^3)
通常情况下,这个64是可以不必考虑的
不过在本题中由于n较小,很遗憾,它能够让你刚好爆时限……
那么怎么通过预处理优化呢?
秘密就在于,我们可以发现,由于黑板上的数范围很小,那么由bool数组存储的状态总数最多有2^8种。
虽然说合并的总方案及其之多,但由于状态总数有限,我们可以得知——
这些状态在合并的过程中,有一些状态是被重复合并了的,其实它们只要算一次就好了。
所以,得出结论:用一个8位二进制数代表一种状态,两个8位状态合并,会产生它们乘积数量的合并总可能性
即为2^16种可能出现的总合并情况。
我们把这些先预处理,即形成了一个2^16-->2^8的映射。
在真正需要合并的时候,只要读取对应的映射值就好了。
这样的复杂度显然是O(1)的。
于是优化后的时间复杂度即为O(2^16+n^3)。
AC!!!
另外闲扯一下 DB皮皮虾 聚聚 XN同学的AC算法。
这是你从未想过的AC算法。
是这样的:
话说JXFZ的雏鹰班真的是人才辈出,
一个初一的 DB皮皮虾 聚聚 XN 同学,
居然 猜 推出了比正解更优秀的算法!!!
他通过 疯狂打表大胆胡猜 严谨推理小心论证,发现了最终得出的可能值一定是一段连续的区间!!!
咳咳,于是!!!就只要通过贪心(排序不等式原理),
来按照顺序合并,并且每次对新生成的数进行二分插入,
处理两次,就可以得到两个极值了。
再综合 XN皮皮虾之究极定理 ,可以知道:这个序列就是答案了!
效率竟高达O(n log n)!!!简直跑得比香港记者还快!!!
在此,我只想赞叹一句——
太强了!太强了!太强了!太强了!太强了!太强了!
Orz Orz Orz Orz Orz Orz
%%%%%%
666666
……
(虽然说,偷偷告诉你一个秘密?——这个算法其实是错的嘿嘿嘿…)
此话怎讲?
另一个初一的 聚聚 WYX 同学又凭借他极富远见的思维与强大的计算与构造能力
发现了一桩反例——
那就是1,2,7;
从而成功Hack掉了XN的迷之AC算法。
真的是更让我膜到不行!!!
然而还是得提一提XN的算法,
其实还是非常优秀的!
尤其在这种n挺大(相对而言),范围很小的数据,
正确的数学期望极大!
于是就在如此高效的时限能很大地保证了程序大部的正确性!
哦哦,还有一件事,顺便提一下我考场上的迷之70分算法吧。
我发现:对于随机的数据,我们有很大的数学期望会使得一部分数所求出的答案会与整体的答案一样
(因为每个数的取值是非常狭隘的,而每个数对最终结果的贡献也随着合并的次数递减)
另外,我们发现两侧的数肯定是比中间的数更具有代表性的。
于是就有了一种这样的 可爱的Charlotte算法 ——
不管你 n 多大(几个亿其实都无所谓)
我只保留前6个数与后6个数。(6很吉利,可以大大增加正确率)
生成一个十二元素的序列。
用爆力的指数级算法去搞定它!
再加上一些奇奇怪怪的特判和补充考虑,
然后就把得到的答案输出了! 哈哈哈哈哈哈哈哈哈!!!
惊不惊喜,意不意外?是不是非常神奇?!!
我坚信:暴力出奇迹!
而且耗时也只是O(2^12)的哦!
就拿下来70%的数据!(貌似本机测随机数据的话正确率更高)
代码实现也格外简单!在考场上,十分实惠!
这大概就是关于这一道题的全部了。
衷心希望能够对您有所帮助或启迪!
(觉得好要不来赏个赞?)