可以动态翻转和查询的 01最长不降序列

美团点评的最后一道笔试题(算法方向,第五道题)

题意:给定一个n(1到100000),m(1到100000),一个长度为n的01数组(只有0和1),然后m行操作,操作要么是查询当前最长不降序列长度,要么是给定x和y,翻转数组中x到y的一段(即位于[x,y]区间的数,0变成1,1变成0)。

题解:我用的是线段树,实现起来三四百行,有更好的办法欢迎交流。

首先考虑一个01数组计算最长不降序列的办法。由于是01序列,所以不降序列具有形式00001111,即前面为0,后面为1的序列(可以没有1或0)。所以只需要对每个位置,计算出当前位置前面的0的个数和后面的1的个数的和,取最大值就是最长不降序列的长度。

接着考虑翻转,我们可以维护上面提到的和值,然后查询的时候取最大即可。问题是翻转的时候怎么维护。设翻转区间为[x,y],则整个数组分成3段,[0,x - 1], [x,y], [y+1, end]
设zero[i]为位置i前面(包括自己)0的个数,为位置i后面(包括自己)1的个数,我们维护的是sum[i] = zero[i] + one[i],以及最大值

对于[0, x-1]这一段,zero[i]不变,而one[i]的改变都一致,增加的为[x,y]这一段中原来0比1多的个数为 -diff(x, y)(设diff(x,y)为[x,y]中1比0多的个数)
对于[y + 1,end]也一样,one[i]不变,zero[i]增加的 个数为diff(x,y)

所以我们可以用另外一个线段树来维护每一段0的个数(线段树最简单的应用),翻转的时候个数等于区间长度减去当前0的个数,就可以求出diff(x,y)。
而且有了这个线段树,我们也可以在log(n)的时间内求出一个zero[i]或者one[i]

最后我们要维护[x,y]这一段。考虑一个数组整个翻转的情况,比如
0100110,翻转成1011001,则zero[i]和onei都发生了什么改变?
0到位置i共有i+1个位置,所以我们只要知道前面原来有多少个1就是翻转后0的个数,即 n e w Z e r o [ i ] = i + 1 − z e r o [ i ] newZero[i] = i + 1 - zero[i] newZero[i]=i+1zero[i], 同样 n e w O n e [ i ] = n + 1 − i − o n e [ i ] newOne[i] = n + 1 - i - one[i] newOne[i]=n+1ione[i]
现在考虑[x,y]是位于中间,将下标左移到0,先减去[0,x-1]的影响,然后最后加回来,得 n e w Z e r o [ i ] = i − x + 1 − ( z e r o [ i ] − z e r o [ x − 1 ] ) + z e r o [ x − 1 ] = i − x + 1 − z e r o [ i ] + 2 ∗ z e r o [ x − 1 ] newZero[i] = i - x + 1 - (zero[i] - zero[x-1]) + zero[x-1]=i-x+1-zero[i] +2*zero[x-1] newZero[i]=ix+1(zero[i]zero[x1])+zero[x1]=ix+1zero[i]+2zero[x1]
同样 n e w O n e [ i ] = y − i + 1 − o n e [ i ] + 2 ∗ o n e [ y + 1 ] newOne[i] = y - i + 1 - one[i] + 2*one[y+1] newOne[i]=yi+1one[i]+2one[y+1]

虽然newZero和newOne都和具体的下标i相关,但是它们的和 n e w S u m = n e w Z e r o + n e w O n e [ i ] = ( y − x ) + 2 − s u m [ i ] + 2 ∗ ( z e r o [ x − 1 ] + o n e [ y + 1 ] ) newSum = newZero + newOne[i] = (y-x) + 2 - sum[i]+ 2 * (zero[x - 1] + one[y + 1]) new

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值