首先说明一下MEX,设S是自然数集合N的一个子集,那么S的MEX则为min(N\S),即不包含于S的最小自然数。
题目大意是存在一个空集S,提供n组输入(n<10^5),每组输入对应下面的一个指令(1 ≤ l ≤ r ≤ 10^18):
1.将集合[l, r]包含到S中。
2.将集合[l, r]从S中移除。
3.将[l, r]与S的交集从S中移除,并将S\[l, r]加入到S中。(即从S移除所有同时出现在[l, r]与S中的元素,添加所有只出现于[l, r]但不在S中出现的元素)
每个指令都需要打印集合S当前对应的MEX。
鉴于题目中出现了大量有关区间的操作,可以很自然地想到用线段树来解决这个问题。如果对线段树不了解,可以阅读我所写的另外一篇博客:线段树完整分析。
首先我们建立这样的一组区间[0, 10^18],区间[x,x]上保存整数x是否包含在集合S中。
题目中出现的区间的边界l, r范围过大,显然无法真的建立一个大小为10^18的区间树。这时候我们需要将其离散化。注意到作为边界出现的l和r总共只会有不超过10^5个,其总和也不过2e5级别而已,这个级别是可以接受的。我们按照偏序关系将出现的所有l和r通过映射f映射到[0, 2e5]中,并且保证若f(a)<f(b)当且仅当a<b,即保证了原数值的偏序关系。这样的好处是覆盖关系在原数值和映射的像上是等价的,比如[a,b]被[c,d]所覆盖,必然有[f(a),f(b)]被[f(c),f(d)]所覆盖。由于f保证了偏序关系,因此f也是一个双射,因此可以找到f的逆映射g,使得(fg)(x)=x。最后将[0,2e5]区间借助逆映射g可以还原到[0,10e18]空间中,保持原来的覆盖关系。解析这些覆盖属性就可以得出MEX。
接下来说一下如何找到映射f。首先我们可以存储所有的输入的l,r-1,r,r+1到数组M中(这样做可以避免边界覆盖时会出现的问题),同时还需要将1和一个代表无穷值的最大值INFINITE存入数组中,这里花费的时间复杂度为O(n)。之后将数组进行排序,这需要O(nlog2(n))的时间复杂度。最后将排序好的数组重新处理,移除所有重复的元素,每个相同元素只保留一个,这需要O(n)的时间复杂度。这时候映射f和g就建立好了。g(x) = M[x],计算速度为O(1)。而f(x)则可以在M中用二分查找法快速查找对应的存储下标,时间复杂度为O(log2(n))。预处理的总共的时间复杂度为O(n)+O(nlog2(n))+O(n)=O(nlog2(n))。
接下来我们的区间树应该用来表示区间[0, M.length-2],其中[x, x]表示[g(x), g(x+1))是否包含于S内,为1表示包含,0表示不包含。同样[x, y]由于是[x,x],...,[y,y]的并集,因此可以很自然地表示[g(x),g(x+1)), ... ,[g(y), g(y+1))的并集[g(x),g(y+1))是否包含在集合中。而[0, M.length-2]表示了区间[1,INFINITE)。注意上面我们多个较小的空间的连续拼接映射为无穷长的自然数区间,且这些小区间均不相交,因此MEX必定落在这样的一个区间[g(x), g(x+1))中,其中0<x<M.length。
先介绍下这里使用的线段树结点的属性定义:
- leftChild, rightChild,分别是左子结点和右子结点
- leftBound, rightBound, 表示线段树结点代表的区间范围[leftBound, rightBound]
- cachedMinValue, cachedMaxValue, 缓存数组A区间[leftBound, rightBound]中的最小值和最大值。实际上缓存的是子结点提供的最大最小值。
- dirtyMark,保存当前缓存未被执行的指令:0表示无指令,1表示批量置1操作(对应请求1),2表示批量置0操作(对应请求2),3代表逆操作(1设为0,设为1,对应请求3)。父结点的dirtyMark将在子结点的dirtyMark生效之后才生效,即拥有更低的优先级(同样意味着更加直接地影响最终结果)。
建立线段树的过程不再缀述(O(n)时间复杂度),这里只介绍区间更新操作和MEX查询操作。对于区间操作,其思路与区间树类似,均是通过缓存标记来减少实际更新结点数目。这里需要注意的是对一个结点代表的区间的全体更新和局部更新是有一定区别的。整体更新的情况下:如果对结点node整体执行某个指令,
如果指令是1或2,那么显然前面对node做的一切动作都将被无效化,即将dirtyMark设置为指令即可;
如果指令是3,则需要视情况决定:
如果原来的dirtyMark为0,则替换为3
如果原来的dirtyMark为1,则当1执行完毕后当前结点和子结点的数据都为1,因此指令3的价值与指令2一致,因此将dirtyMark设置为2。
如果原来的dirtyMark为2,则dirtyMark设置为1,推理如上。
如果原来的dirtyMark为3,则两个操作相互冲销,dirtyMark变为0。
所有整体更新都不会继续更新子结点(因为当前的结点的dirtyMark足以缓存指令),这就是整体更新的全部情况。当进行局部更新,情况就要变得复杂很多:
如果原来的指令是0,则可以不处理当前结点,直接将指令传递给子结点。并利用子结点回溯当前结点。
如果原来的指令不是0,即还有未被执行的原指令存在,则首先将原来的dirtyMark传递给子结点,这样就可以清空自己的dirtyMark。之后按照指令为0的情况继续下去。
转化为代码为:
update(node, l, r, dirtyMark)
if(l > node.rightBound || r < node.leftBound)
return
if(l <= node.leftBound && r >= node.rightBound) //整体更新
if(dirtyMark != 3)
node.dirtyMark = dirtyMark
else
node.dirtyMark = 3 - node.dirtyMark
else //局部更新
if(node.dirtyMark != 0)
update(node.leftChild, node.leftBound, node.rightBound, node.dirtyMark)
update(node.rightChild, node.leftBound, node.rightBound, node.dirtyMark)
node.dirtyMark = 0
update(node.leftChild, l, r, dirtyMark)
update(node.rightChild, l, r, dirtyMark)
node.cachedMinValue = min(QueryMin(node.leftChild), QueryMin(node.rightChild))
node.cachedMaxValue = max(QueryMax(node.leftChild), QueryMax(node.rightChild))
上面用到了两个函数QueryMin和QueryMax,下面给出QueryMin的定义:
QueryMin(node)
if(node.dirtyMark == 0)
return node.cachedMinValue
else if(node.dirtyMark == 1)
return 1
else if(node.dirtyMark == 2)
return 0
else
return (cachedMaxValue + 1) % 2
而QueryMax的定义与QueryMin基本一致,只是对调cachedMaxValue和cachedMinValue出现的位置而已,不难发现这是一个时间复杂度为O(1)的函数。
下面说明update的时间复杂度:不考虑递归的时间花费,考虑其在单个结点上的执行情况。在遇到整体更新的情况,处理非常简单,时间复杂度为O(1)。而当遇到局部更新的时候,最复杂的情况下是向下传递原dirtyMark,但由于是整体更新,因此其这两个步骤的加总时间复杂度依旧保持为2*O(1)=O(1),而通过QueryMin和QueryMax重新计算缓存的最大值和最小值的时间复杂度为2*O(1)=O(1),因此处理一个结点的总的时间复杂度为O(1)+O(1)=O(1)。与线段树的更新操作分析一样,update总共会经历O(log2(n))个结点,因此update的总的时间复杂度为O(1)*O(log2(n))=O(log2(n))。
下面说明快速查询MEX,由于区间树表示了S与区间[1,INFINITE)之间的包含关系,因此必定有一个叶子结点[x,x],其在g的像下完整包含了MEX:
QueryMEX(node, reverse)
if(node.leftBound == node.rightBound)
return M[node.leftBound]
if(node.dirtyMark == 3)
if(reverse == 1)
reverse = 0
else
reverse = 1
if(reverse == 0)
if(node.dirtyMark == 2)
return node.leftBound
if(QueryMin(node.leftChild) == 0)
QueryMEX(node.leftChild, reverse)
return QueryMEX(node.rightChild, reverse)
else
if(node.dirtyMark == 1)
return node.leftBound
if(QueryMax(node.leftChild) == 1)
QueryMEX(node.leftChild, reverse)
return QueryMEX(node.rightChild, reverse)
当我们需要向根结点查找最小的单元素区间[x,x],使得[x,x]保存的值为0。这时候我们可以维护一个逆转标志reverse。当reverse为真表示这个当前结点的值在回溯时将被逆转,显然两次逆转等价于不逆转。这里需要对是否为逆转情况做判断,并向子结点查询不同的值。在向子结点查询最小最大值的时候依旧需要附加上自己的逆转标志,即自身的dirtyMark是否为3。这样子结点才能知道自己调用则希望从自己这里找到一个什么值(0或1)。
上面的时间复杂度为O(log2(n)),因为处理单个结点的时间复杂度为O(1),而递归总共发生了O(log2(n))次(取决于区间树的高度),因此时间复杂度可以简单计算为O(log2(n))*O(1)=O(log2(n))。
上面的update和QueryMEX分别用于处理用外部指令输入,以及对应的MEX的输出。每次输入都需要调用一次update和一次QueryMEX,一次总的时间复杂度为O(log2(n))+O(log2(n))=O(log2(n)),总共发生n次,故,可以计算为O(log2(n))*n=O(nlog2(n))。添加上前面预处理的时间复杂度,总时间复杂度为O(nlog2(n))+O(nlog2(n))=O(nlog2(n))。因此可以在O(nlog2(n))时间复杂度下解决这个问题。
最后附加上java版本的AC代码:
package cn.dalt.codeforces; import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; /** * Created by Administrator on 2017/6/30. */ public class MEXQueries { public static void main(String[] args) throws Exception { AcmInputReader reader = new AcmInputReader(System.in); int num = reader.nextInteger(); if (num == 0) { return; } int[] cmds = new int[num]; long[] leftBounds = new long[num]; long[] rightBounds = new long[num]; //read all input into memory for (int i = 0; i < num; i++) { cmds[i] = reader.nextInteger(); leftBounds[i] = reader.nextLong(); rightBounds[i] = reader.nextLong(); } //Generate g long[] g = new long[num * 4 + 2]; System.arraycopy(leftBounds, 0, g, 0, num); System.arraycopy(rightBounds, 0, g, num, num); for (int i = 0, base1 = num * 2, base2 = num * 3; i < num; i++) { g[base1 + i] = Math.max(rightBounds[i] - 1, 1); g[base2 + i] = rightBounds[i] + 1; } g[num * 4] = 1; g[num * 4 + 1] = Long.MAX_VALUE; Arrays.sort(g); //Remove all repeated elements int gLen = 0; for (int i = 1, bound = g.length; i < bound; i++) { if (g[i] != g[gLen]) { g[++gLen] = g[i]; } } gLen++; //Build section tree SectionTree tree = new SectionTree(0, gLen); //Handle all cmds and output the MEX for (int i = 0; i < num; i++) { int cmd = cmds[i]; int leftBound = Arrays.binarySearch(g, 0, gLen, leftBounds[i]); int rightBound = Arrays.binarySearch(g, 0, gLen, rightBounds[i] + 1) - 1; tree.update(leftBound, rightBound, cmd); //Query all information // for (int j = 0; j < gLen - 1; j++) { // if (tree.queryValue(j, j).maxValue == 1) { // System.out.print(String.format("[%d, %d], ", g[j], g[j + 1] - 1)); // } // } // System.out.println(); System.out.println(g[tree.queryMEX()]); } } /** * @author dalt * @see java.lang.AutoCloseable * @since java1.7 */ private static class AcmInputReader implements AutoCloseable { private PushbackInputStream in; @Override public void close() throws IOException { in.close(); } private static class AsciiMarksLazyHolder { public static final byte BLANK_MARK = 1; public static final byte SIGN_MARK = 1 << 1; public static final byte NUMERAL_MARK = 1 << 2; public static final byte UPPERCASE_LETTER_MARK = 1 << 3; public static final byte LOWERCASE_LETTER_MARK = 1 << 4; public static final byte LETTER_MARK = UPPERCASE_LETTER_MARK | LOWERCASE_LETTER_MARK; public static final byte EOF = 1 << 5; public static byte[] asciiMarks = new byte[256]; static { for (int i = 0; i <= 32; i++) { asciiMarks[i] = BLANK_MARK; } asciiMarks['+'] = SIGN_MARK; asciiMarks['-'] = SIGN_MARK; for (int i = '0'; i <= '9'; i++) { asciiMarks[i] = NUMERAL_MARK; } for (int i = 'a'; i <= 'z'; i++) { asciiMarks[i] = LOWERCASE_LETTER_MARK; } for (int i = 'A'; i <= 'Z'; i++) { asciiMarks[i] = UPPERCASE_LETTER_MARK; } asciiMarks[0xff] = EOF; } } /** * 创建读取器 * * @param input 输入流 */ public AcmInputReader(InputStream input) { in = new PushbackInputStream(new BufferedInputStream(input)); } private int nextByte() throws IOException { return in.read() & 0xff; } /** * 如果下一个字节为b,则跳过该字节 * * @param b 被跳过的字节值 * @throws IOException if 输入流读取错误 */ public void skipByte(int b) throws IOException { int c; if ((c = nextByte()) != b) { in.unread(c); } } /** * 如果后续k个字节均为b,则跳过k个字节。这里{@literal k<times} * * @param b 被跳过的字节值 * @param times 跳过次数,-1表示无穷 * @throws IOException if 输入流读取错误 */ public void skipByte(int b, int times) throws IOException { int c; while ((c = nextByte()) == b && times > 0) { times--; } if (c != b) { in.unread(c); } } /** * 类似于{@link #skipByte(int, int)}, 但是会跳过中间出现的空白字符。 * * @param b 被跳过的字节值 * @param times 跳过次数,-1表示无穷 * @throws IOException if 输入流读取错误 */ public void skipBlankAndByte(int b, int times) throws IOException { int c; skipBlank(); while ((c = nextByte()) == b && times > 0) { times--; skipBlank(); } if (c != b) { in.unread(c); } } /** * 读取下一块不含空白字符的字符块 * * @return 下一块不含空白字符的字符块 * @throws IOException if 输入流读取错误 */ public String nextBlock() throws IOException { skipBlank(); StringBuilder sb = new StringBuilder(); int c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c = nextByte()] != AsciiMarksLazyHolder.BLANK_MARK) { sb.append((char) c); } in.unread(c); return sb.toString(); } /** * 跳过输入流中后续空白字符 * * @throws IOException if 输入流读取错误 */ private void skipBlank() throws IOException { int c; while ((c = nextByte()) <= 32) ; in.unread(c); } /** * 读取下一个整数(可正可负),这里没有对溢出做判断 * * @return 下一个整数值 * @throws IOException if 输入流读取错误 */ public int nextInteger() throws IOException { skipBlank(); int value = 0; boolean positive = true; int c = nextByte(); if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { positive = c == '+'; } else { value = '0' - c; } c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { value = (value << 3) + (value << 1) + '0' - c; c = nextByte(); } in.unread(c); return positive ? -value : value; } /** * 判断是否到了文件结尾 * * @return true如果到了文件结尾,否则false * @throws IOException if 输入流读取错误 */ public boolean isMeetEOF() throws IOException { int c = nextByte(); if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { return true; } in.unread(c); return false; } /** * 判断是否在跳过空白字符后抵达文件结尾 * * @return true如果到了文件结尾,否则false * @throws IOException if 输入流读取错误 */ public boolean isMeetBlankAndEOF() throws IOException { skipBlank(); int c = nextByte(); if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { return true; } in.unread(c); return false; } /** * 获取下一个用英文字母组成的单词 * * @return 下一个用英文字母组成的单词 */ public String nextWord() throws IOException { StringBuilder sb = new StringBuilder(16); skipBlank(); int c; while ((AsciiMarksLazyHolder.asciiMarks[(c = nextByte())] & AsciiMarksLazyHolder.LETTER_MARK) != 0) { sb.append((char) c); } in.unread(c); return sb.toString(); } /** * 读取下一个长整数(可正可负),这里没有对溢出做判断 * * @return 下一个长整数值 * @throws IOException if 输入流读取错误 */ public long nextLong() throws IOException { skipBlank(); long value = 0; boolean positive = true; int c = nextByte(); if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { positive = c == '+'; } else { value = '0' - c; } c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { value = (value << 3) + (value << 1) + '0' - c; c = nextByte(); } in.unread(c); return positive ? -value : value; } /** * 读取下一个浮点数(可正可负),浮点数是近似值 * * @return 下一个浮点数值 * @throws IOException if 输入流读取错误 */ public float nextFloat() throws IOException { return (float) nextDouble(); } /** * 读取下一个浮点数(可正可负),浮点数是近似值 * * @return 下一个浮点数值 * @throws IOException if 输入流读取错误 */ public double nextDouble() throws IOException { skipBlank(); double value = 0; boolean positive = true; int c = nextByte(); if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { positive = c == '+'; } else { value = c - '0'; } c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { value = value * 10.0 + c - '0'; c = nextByte(); } if (c == '.') { double littlePart = 0; double base = 1; c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { littlePart = littlePart * 10.0 + c - '0'; base *= 10.0; c = nextByte(); } value += littlePart / base; } in.unread(c); return positive ? value : -value; } /** * 读取下一个高精度数值 * * @return 下一个高精度数值 * @throws IOException if 输入流读取错误 */ public BigDecimal nextDecimal() throws IOException { skipBlank(); StringBuilder sb = new StringBuilder(); sb.append((char) nextByte()); int c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { sb.append((char) c); c = nextByte(); } if (c == '.') { sb.append('.'); c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { sb.append((char) c); c = nextByte(); } } in.unread(c); return new BigDecimal(sb.toString()); } /** * 读取下一个大整数数值 * * @return 下一个大整数数值 * @throws IOException if 输入流读取错误 */ public BigInteger nextBigInteger() throws IOException { skipBlank(); StringBuilder sb = new StringBuilder(); sb.append((char) nextByte()); int c = nextByte(); while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { sb.append((char) c); c = nextByte(); } in.unread(c); return new BigInteger(sb.toString()); } } private static /** * Created by F on 2017/6/29. */ class SectionTree { /** * 根结点 */ private SectionTreeNode root; /** * 代表最大值和最小值的值对 */ public static class ValuePair { public static final ValuePair EMPTY_VALUE_PAIR = new ValuePair(Integer.MAX_VALUE, Integer.MIN_VALUE); public final int minValue; public final int maxValue; public ValuePair(int minValue, int maxValue) { this.minValue = minValue; this.maxValue = maxValue; } @Override public String toString() { return String.format("max:%d, min:%d", maxValue, minValue); } public static ValuePair combine(ValuePair a, ValuePair b) { return new ValuePair(Math.min(a.minValue, b.minValue), Math.max(a.maxValue, b.maxValue)); } } /** * 区间类型 */ private static class Section { /** * 两个区间的关系,分别为被包含,相交和无覆盖 */ public static enum SectionRelation { INCLUDED, INTERSECT, NONE } private int leftBound, rightBound; public int getLeftBound() { return leftBound; } public int getRightBound() { return rightBound; } public Section(int leftBound, int rightBound) { if (rightBound < leftBound) { throw new IllegalArgumentException(String.format("A section should obey leftBound<=rightBound, but current state is %d > %d", leftBound, rightBound)); } this.leftBound = leftBound; this.rightBound = rightBound; } /** * 计算this和other两个区间的关系 * * @param other 另外一个区间 * @return this和other的关系 */ public SectionRelation relationWith(Section other) { if (rightBound < other.leftBound || leftBound > other.rightBound) { return SectionRelation.NONE; } if (rightBound <= other.rightBound && leftBound >= other.leftBound) { return SectionRelation.INCLUDED; } return SectionRelation.INTERSECT; } /** * 提取左半区间 * * @return 左半区间 */ public Section leftHalfSection() { return new Section(leftBound, (leftBound + rightBound) / 2); } /** * 提取右半区间 * * @return 右半区间 */ public Section rightHalfSection() { return new Section((leftBound + rightBound) / 2 + 1, rightBound); } @Override public String toString() { return String.format("[%d, %d]", leftBound, rightBound); } /** * 获取区间中覆盖的整数数目 * * @return 区间中覆盖的整数数目 */ public int size() { return rightBound - leftBound + 1; } /** * 快速判断当前区间是否只覆盖了一个整数 * * @return true表示只覆盖了一个整数,否则返回false */ public boolean containSingleElement() { return rightBound == leftBound; } } /** * 区间树结点 */ private static class SectionTreeNode { public SectionTreeNode leftChild, rightChild; private Section section; private int cachedMinValue, cachedMaxValue; private int dirtyMark; /** * 获取结点所代表的区间 * * @return */ public Section getSection() { return section; } /** * 设置肮脏标志 * * @param dirtyMark 肮脏标志(修改值) */ public void setDirtyMark(int dirtyMark) { this.dirtyMark += dirtyMark; } /** * 构造代表section区间的区间树结点 * * @param section 一个区间 */ public SectionTreeNode(Section section) { this.section = section; } /** * 获取结点所代表的区间中的最小值 * * @return 结点所代表的区间中的最小值 */ public int getMinValue() { switch (dirtyMark) { case 0: return cachedMinValue; case 1: return 1; case 2: return 0; default: return (cachedMaxValue + 1) % 2; } } /** * 获取结点所代表的区间中的最大值 * * @return 结点所代表的区间中的最大值 */ public int getMaxValue() { switch (dirtyMark) { case 0: return cachedMaxValue; case 1: return 1; case 2: return 0; default: return (cachedMinValue + 1) % 2; } } /** * 利用子结点中保存的信息更新当前结点内部缓存的最小值和最大值 */ public void updateCachedValue() { cachedMinValue = Math.min(leftChild.getMinValue(), rightChild.getMinValue()); cachedMaxValue = Math.max(leftChild.getMaxValue(), rightChild.getMaxValue()); } @Override public String toString() { StringBuilder sb = new StringBuilder(String.format("{lrBound:%s,minMaxValue:[%d,%d],dirtyMark:%d(", section.toString(), cachedMinValue, cachedMaxValue, dirtyMark)); if (leftChild != null) { sb.append(leftChild.toString()); } sb.append(", "); if (rightChild != null) { sb.append(rightChild.toString()); } sb.append(")}"); return sb.toString(); } } /** * 利用数组data构建一株区间树 * * @param data 数组 */ public SectionTree(int[] data) { this(data, 0, data.length); } /** * 利用数组data构建一株区间树 * * @param data 数组 */ public SectionTree(int[] data, int from, int to) { if (from >= to) { throw new IllegalArgumentException("You can't build a section tree with empty array"); } this.root = buildTreeNode(data, new Section(from, to - 1)); } @Override public String toString() { return root.toString(); } /** * 更新[leftBound, rightBound]中所有的整数,令其增加val * * @param leftBound 区间左边界 * @param rightBound 区间右边界 * @param val 修正值 */ public void update(int leftBound, int rightBound, int val) { update(root, new Section(leftBound, rightBound), val); } /** * 查询[leftBound, rightBound]中的最大值和最小值 * * @param leftBound 区间左边界 * @param rightBound 区间右边界 * @return 保存了最大值和最小值的ValuePair对象 */ public ValuePair queryValue(int leftBound, int rightBound) { return queryValue(root, new Section(leftBound, rightBound)); } public static ValuePair queryValue(SectionTreeNode node, Section section) { switch (node.getSection().relationWith(section)) { case INCLUDED: return new ValuePair(node.getMinValue(), node.getMaxValue()); case INTERSECT: ValuePair vp = ValuePair.combine(queryValue(node.leftChild, section), queryValue(node.rightChild, section)); int min = vp.minValue; int max = vp.maxValue; switch (node.dirtyMark) { case 0: return vp; case 1: return new ValuePair(1, 1); case 2: return new ValuePair(0, 0); case 3: return new ValuePair((max + 1) % 2, (min + 1) % 2); } } return ValuePair.EMPTY_VALUE_PAIR; } public static void update(SectionTreeNode node, Section section, int val) { switch (node.getSection().relationWith(section)) { case INCLUDED: if (val != 3) { node.dirtyMark = val; } else { node.dirtyMark = 3 - node.dirtyMark; } return; case INTERSECT: if (node.dirtyMark != 0) { update(node.leftChild, node.section, node.dirtyMark); update(node.rightChild, node.section, node.dirtyMark); node.dirtyMark = 0; } update(node.leftChild, section, val); update(node.rightChild, section, val); node.updateCachedValue(); return; case NONE: return; } } private static SectionTreeNode buildTreeNode(int[] data, Section section) { SectionTreeNode current = new SectionTreeNode(section); if (section.containSingleElement()) { current.cachedMinValue = current.cachedMaxValue = data[section.getLeftBound()]; return current; } current.leftChild = buildTreeNode(data, section.leftHalfSection()); current.rightChild = buildTreeNode(data, section.rightHalfSection()); current.updateCachedValue(); return current; } public SectionTree(int from, int to) { if (to <= from) { throw new IllegalArgumentException("You can't build a section tree with empty array"); } this.root = buildZeroizeTreeNode(new Section(from, to - 1)); } private static SectionTreeNode buildZeroizeTreeNode(Section section) { SectionTreeNode current = new SectionTreeNode(section); if (section.containSingleElement()) { return current; } current.leftChild = buildZeroizeTreeNode(section.leftHalfSection()); current.rightChild = buildZeroizeTreeNode(section.rightHalfSection()); return current; } public int queryMEX() { return queryMEX(root, 0); } private static int queryMEX(SectionTreeNode node, int reverse) { if (node.getSection().containSingleElement()) { return node.getSection().leftBound; } reverse = reverse ^ (node.dirtyMark == 3 ? 1 : 0); if (reverse == 0) { if (node.dirtyMark == 2) { return node.section.leftBound; } if (node.leftChild.getMinValue() == 0) { return queryMEX(node.leftChild, reverse); } return queryMEX(node.rightChild, reverse); } else { if (node.dirtyMark == 1) { return node.section.leftBound; } if (node.leftChild.getMaxValue() == 1) { return queryMEX(node.leftChild, reverse); } return queryMEX(node.rightChild, reverse); } } } }