这套题还是有点质量的吧 ……
A. XORinacci
傻叉签到题,因为异或的性质所以这个序列的循环节长度只有 (3) ……
B. Uniqueness
因为序列长度乃至数的种类都不超过 (2000),考虑先把序列离散化。
题意让我们求一个最短的区间满足如下性质,对于每一种数,其在此区间出现次数不小于在原序列中的出现次数减 (1)。
可以先前缀和求一下对于每种数,当前位置及之前的出现次数,和至少一共需要删掉多少个这种数,即在原序列中的出现次数减 (1),方便以后的计算。
然后双指针确定一个这个区间即可,因为支持$ O(n^2)$ 的算法,所以 (for) 每一种数暴力 (Check)。
C. Magic Grid
结论题,看到 (n) 是 (4) 的倍数就自然想到将网格拆成若干个 (4 * 4) 的网格来做,每一部分网格依然满足题意的性质,并且拼起来也使大网格满足题意性质。
结果发现 (2 * 2) 的网格即可满足性质…… 对于每一组连续的 (4) 个数,存在一种构造方法满足上述性质。
不明白就看代码吧,挺简单的。
D. Restore Permutation
一道傻叉线段树因为写错递归的函数名调了半个多小时…… 属实降智了……
一开始读错题,看成是 (s_i) 表示 (i) 之前满足 (p_j < p_i) 的数的个数,那这道题目的套路就很常见,从后向前推,最后一个数就是当前未选的数中的第 (s_i + 1) 个数。
而正确的题意可谓从这上面发展而来,(s_i) 表示 (i) 之前满足 (p_j < p_i) 的数值之和。相当于把上述题意中,一个数的贡献从 (1) 改为了其数值而已。这样,用线段树维护前缀和,每次在上面二分查应该到哪个位置,即当前的数,然后选了的数就单点修改为 (0) 来删除对前缀和的贡献。
至于一些细节,思路清晰的话试一下就出来了。
E. Let Them Slide
没来得及写,不过真的没想到只做 (4) 题也上分了……
容易发现每一行都是独立的,对每一列,我们只需要把每一行能对这个位置做的最大贡献加起来就好了,所以对每一行单独处理。
设 (len) 为当前行的序列长度,当 (w > 2 * len) 时,显然区间 ([len + 1, w - len]) 是可以取到每一个数,包括空位置(贡献为 (0))的,对这一段区间可以直接加上 (max(max\_num, 0)),(max\_num) 为序列中最大值。
现在处理区间 ([1, len]) 和 ([w - len + 1, w]),画图总结,对于前者中的每一个位置 (j),能取到的序列中的数为 ([max(0, j - w + len), j]),对于后者,为 ([j - w + len, min(j, len + 1)]),那么贡献就是这段区间中的区间最大值。
注意这两段区间如果存在重叠部分不要重叠区间计两次贡献,至于区间最大值,用 (st) 表处理即可,至于贡献的统计可以随便用数据结构做。
F. Bits And Pieces
思路很巧妙,完全没思路…… 本没脑子选手的水平看来也就半斤八两,到此为止了……
考虑按位与的操作只会让二进制中的 1 变少,因此值域不会变大,可以对每一个数,统计其可以被与出哪些数,并让此数 (x) 对 (cnt[x]) 做出 (1) 的贡献。
巧妙之处在于,如果有两个数都可以通过与运算得到 (x),那让这两个数做按位与,就可以与出一个二进制上只会比 (x) 多出 (1) 而不会少的数,即 (x) 是其二进制的子集。换句话说,如果用它做或运算,那么至少能做出 (x) 所做的贡献。
这样,我们只想要知道有哪些数可以被序列中的两个数做与运算得到。
可以 (Dfs) 爆枚二进制子集来统计其可以被与出哪些数,这样每个数至多是 (O(2^{20})) 的。但是如果一个数已经处理过两次了,也就是说它及其它二进制的子集已经都能被某两个数来与出两次了,那么已经达到了我们的目的,就无需再处理了,所以总渐进时间复杂度是 (O(n)) 的。
我们要求的是当前数与后面某两个数按位或得到的最大值,就可以从上面 (cnt[]) 大于 (2) 的数中找,从高位到低位贪心地让 (0) 变成 (1),这里可以结合代码理解。