这套题仍是有点质量的吧 ……算法
题目连接ubuntu
傻叉签到题,由于异或的性质因此这个序列的循环节长度只有 \(3\) ……数据结构
查看代码ide
由于序列长度乃至数的种类都不超过 \(2000\),考虑先把序列离散化。函数
题意让咱们求一个最短的区间知足以下性质,对于每一种数,其在此区间出现次数不小于在原序列中的出现次数减 \(1\)。spa
能够先前缀和求一下对于每种数,当前位置及以前的出现次数,和至少一共须要删掉多少个这种数,即在原序列中的出现次数减 \(1\),方便之后的计算。指针
而后双指针肯定一个这个区间便可,由于支持$ O(n^2)$ 的算法,因此 \(for\) 每一种数暴力 \(Check\)。rest
查看代码code
结论题,看到 \(n\) 是 \(4\) 的倍数就天然想到将网格拆成若干个 \(4 * 4\) 的网格来作,每一部分网格依然知足题意的性质,而且拼起来也使大网格知足题意性质。递归
结果发现 \(2 * 2\) 的网格便可知足性质…… 对于每一组连续的 \(4\) 个数,存在一种构造方法知足上述性质。
不明白就看代码吧,挺简单的。
一道傻叉线段树由于写错递归的函数名调了半个多小时…… 属实降智了……
一开始读错题,当作是 \(s_i\) 表示 \(i\) 以前知足 \(p_j < p_i\) 的数的个数,那这道题目的套路就很常见,从后向前推,最后一个数就是当前未选的数中的第 \(s_i + 1\) 个数。
而正确的题意可谓从这上面发展而来,\(s_i\) 表示 \(i\) 以前知足 \(p_j < p_i\) 的数值之和。至关于把上述题意中,一个数的贡献从 \(1\) 改成了其数值而已。这样,用线段树维护前缀和,每次在上面二分查应该到哪一个位置,即当前的数,而后选了的数就单点修改成 \(0\) 来删除对前缀和的贡献。
至于一些细节,思路清晰的话试一下就出来了。
没来得及写,不过真的没想到只作 \(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\) 表处理便可,至于贡献的统计能够随便用数据结构作。
思路很巧妙,彻底没思路…… 本没脑子选手的水平看来也就半斤八两,到此为止了……
考虑按位与的操做只会让二进制中的 1 变少,所以值域不会变大,能够对每个数,统计其能够被与出哪些数,并让此数 \(x\) 对 \(cnt[x]\) 作出 \(1\) 的贡献。
巧妙之处在于,若是有两个数均可以经过与运算获得 \(x\),那让这两个数作按位与,就能够与出一个二进制上只会比 \(x\) 多出 \(1\) 而不会少的数,即 \(x\) 是其二进制的子集。换句话说,若是用它作或运算,那么至少能作出 \(x\) 所作的贡献。
这样,咱们只想要知道有哪些数能够被序列中的两个数作与运算获得。
能够 \(Dfs\) 爆枚二进制子集来统计其能够被与出哪些数,这样每一个数至可能是 \(O(2^{20})\) 的。可是若是一个数已经处理过两次了,也就是说它及其它二进制的子集已经都能被某两个数来与出两次了,那么已经达到了咱们的目的,就无需再处理了,因此总渐进时间复杂度是 \(O(n)\) 的。
咱们要求的是当前数与后面某两个数按位或获得的最大值,就能够从上面 \(cnt[]\) 大于 \(2\) 的数中找,从高位到低位贪心地让 \(0\) 变成 \(1\),这里能够结合代码理解。