牛客寒假基础算法训练营1(EFIKLM)

赛时8题(实际上是7题,有道题数据水而被fst了...)

E

这道题赛时做出来了,但看题解时看到了一个值得学习的重要结论。

结论:要将一个数组中的所有数变为相同的数x,操作为加1或减1,那么最小代价 <> x是中位数。

(若不知道这个结论做这个问题,可以将所有数当成若干在数轴上的点,固定点为x,则显然x从负无穷到正无穷的过程中,代价是先减后增的山谷形,这样就可以用 三分 或者 二分斜率 来解决这个问题)。

证明:由括号内容可知最小代价和最终值的二元函数呈山谷形,故这个最终值一定是唯一的。假设这个值不是median。则:

  1. val>median时,数组中比val小的元素个数>val大的元素个数(中位数的性质),前者通过加1达到val,后者通过减1达到val。则将val减少1,会使所有比val小的元素各减少一个代价,同时会使所有比val大的元素各增加一个代价。由于比val小的元素个数比val大的元素个数多,故总代价是减少的。因此最优解一定<val
  2. val>median时,与1完全类似的证明,可以证得最优解一定>val

得证。

code

F

双指针 + 前缀和技巧

要想用传统的“枚举右端点,二分左端点”的套路统计贡献,就必须要发现一些单调性。而可以发现,对于固定的右端点,左端点越偏左,子区间内包含的数的种类就会越多。而所规定的子区间恰好要满足包含种类数为2这一性质,因此可以采用这个套路。

可以用mapsize函数快速获得要维护区间的数的种类数。

剩下的问题就是要判断子区间内两种数的数量是否相同。这里有个比较巧妙的trick:将原序列的数转化为11的映射,这样只需要统计区间和为0的区间个数,即pre[l]==pre[r]的个数。那怎么建立这个映射呢?

可以发现,只需要顺序遍历序列,对于不同的数,11交替赋值即可。由于能够保证子区间的数的种类数最多是2,进而可以保证两种不同的数会被分别赋为11。具体实现见代码。

维护区间内的前缀和数组信息时要特别注意pre[0]的维护。

code

I

code

K

ST表区间gcd + 二分 + 查询trick

与F题做法类似:区间gcd明显具有单调性,因此可以采用“枚举右端点,二分左端点”的套路。

而还要记住区间gcd的另一个重要性质:对于一个数组的所有子数组,不同gcd的数量最多为O(nlogA)A为数组中最大元素。

证明:对于某个特定的右端点r,考虑所有左端点1<=l<=r,所表示的[l,r]区间:它们的gcd必然均为a[r]的约数。而a[r]>1因子数量应为O(logA)级别,在左端点不断往左扩展时,gcd值减小,且是按因子个数不断减小的(相当于每次减小会除以某一个因子,这是证明该性质的关键!)因此,所有不同的gcd个数不会超过a[r]的因子的个数,即logA;对于所有的右端点均是这样,因此所有子区间的不同gcd个数约为nlogA个。

这样,对于每一个右端点统计贡献时,就可以暴力寻找每一个左端点,当然也不是纯暴力,需要利用ST表+二分来找,由上述证明知寻找次数不超过logA次。

这样,问题就转化为:已知若干 右端点固定,左端点连续 的区间gcdG,找其中异或和也为G的区间数量。

不难想到前缀异或和来优化这件事情,即对于区间[l,r]

prexor[r] ^ prexor[l1]=G

由异或交换律,将prexor[r]移到左边,得:

prexor[l1]=G ^ prexor[r]

问题转化为求左端点l1<=x<=r1(注意有偏移,因为求前缀和的左端点是需要偏移的),prexor值为 G ^ prexor[r]的位置x的个数。这个可以用在mapvector上二分来做到静态查询。具体见代码。

code

L

code

M(不会证明)

fst的那道题。赛时猜了个结论没想到直接就过了,赛后才发现自己对这道题还缺乏很多思考。

首先有个结论:最小值是一定要乘2的。这个证明到现在还是不太会,只能靠感性理解。

知道了这个结论,就不难想到每次从最小值位置开始,暴力地向次小值位置扩展区间并更新答案。

code

posted @   jxs123  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示
主题色彩