关于莫队的区间端点移动顺序
查看原帖
关于莫队的区间端点移动顺序
25008
Sweetlemon楼主2020/8/9 12:32

在写莫队的时候,常用到这样的四个循环(以下代码摘自 OI Wiki 普通莫队算法):

while (l < a[i].l) del(c[l++]);
while (l > a[i].l) add(c[--l]);
while (r < a[i].r) add(c[++r]);
while (r > a[i].r) del(c[r--]);

在有些题目中,我们需要保证 [l,r][l,r] 在移动端点的过程中始终是合法区间,即 lr+1l\le r+1 恒成立(l=r+1l=r+1 表示空区间)。此时,每个元素被加入的次数都是非负整数。

因为考虑莫队区间的移动过程,从开始到这时,相当于加入了 [1,r][1,r] 的元素又再删除了 [1,l1][1,l-1] 的元素,因此:

  • 对于 lrl\le r 的情况,[1,l1][1,l-1] 的元素相当于被加入了一次又被删除了一次,[l,r][l,r] 的元素被加入一次,[r+1,+)[r+1,+\infty) 的元素没有被加入
  • 对于 l=r+1l=r+1 的情况,[1,r][1,r] 的元素相当于被加入了一次又被删除了一次,[r+1,+)[r+1,+\infty) 的元素没有被加入
  • 如果 l>r+1l>r+1,那么 [r+1,l1][r+1,l-1](这个区间非空)的元素被删除了一次但没有被加入,因此这个元素被加入的次数是负数

上面我们证明了,如果某时刻出现 l>r+1l>r+1 的情况,那么会存在一个元素的加入次数是负数。这在某些题目会造成问题,例如我们如果用一个 set 维护区间中的所有数,就会出现“需要删除 set 中不存在的元素”的问题。

综上,很多时候保持 lr+1l\le r+1 始终成立是必要的。

然而,上面这种典型的写法不能保证 lr+1l\le r+1 恒成立。令 ll' 表示 a[i].lrr' 表示 a[i].r,那么当 l<r<l<rl<r<l'<r' 时,上面的写法会先将左端点右移到 ll’,可能造成 l>r+1l>r+1

这四个循环一共有 4!=244!=24 种排列顺序,我枚举了第一个循环是操作左端点的这 1212 种排列(另外 1212 种是对称的),并写出了正确性和反例。

循环顺序正确性反例
l--,l++,r--,r++错误l<r<l<rl<r<l'<r'
l--,l++,r++,r--错误l<r<l<rl<r<l'<r'
l--,r--,l++,r++错误l<r<l<rl<r<l'<r'
l--,r--,r++,l++正确
l--,r++,l++,r--正确
l--,r++,r--,l++正确
l++,l--,r--,r++错误l<r<l<rl<r<l'<r'
l++,l--,r++,r--错误l<r<l<rl<r<l'<r'
l++,r++,l--,r--错误l<r<l<rl<r<l'<r'
l++,r++,r--,l--错误l<r<l<rl<r<l'<r'
l++,r--,l--,r++错误l<r<l<rl<r<l'<r'
l++,r--,r++,l--错误l<r<l<rl<r<l'<r'

因此,2424 种排列中只有 66 种是正确的,其中有 22 种的证明较繁琐,这里只给出其中 44 种的证明。

44 种正确写法的共同特点是,前两步先扩大区间(l--r++),后两步再缩小区间(l++r--)。这样写,前两步由于是扩大区间,因此区间一定不会变非法;执行完前两步后,llrrl\le l'\le r'\le r 一定成立,此时执行后两步只会把区间缩小到 [l,r][l',r'],不会造成区间不合法。

翻阅此题的题解,许多题解的写法是前两种(先操作左端点,再操作右端点)或后六种(第一步就是缩小区间),这些写法虽然在此题可能不会造成很大问题,但由于此题有莫队模板的性质,可能会造成新学莫队的 OIer 此后写莫队时出错。

而 OI Wiki 上更是用粗体标出“注意:由于 ++l--r 的存在,下面代码中的移动区间的 4 个 for 循环的位置很关键,不能改变它们之间的位置关系”,我认为这个错误比较严重。如果我的这些讨论没有错误,我将到 OI Wiki 上对此段代码进行修改。

本人水平有限,以上内容如有错误,敬请指正。

2020/8/9 12:32
加载中...