首先说明一下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);
             }
         }
     }
 }

codeforces:MEX Queries分析和实现的更多相关文章

  1. [Codeforces]817F. MEX Queries 离散化+线段树维护

    [Codeforces]817F. MEX Queries You are given a set of integer numbers, initially it is empty. You sho ...

  2. Educational Codeforces Round 23 F. MEX Queries 离散化+线段树

    F. MEX Queries time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...

  3. Codeforces 817F MEX Queries

    题意:对一个维护三种操作:1.将[l..r]中的数全部加入集合中.2.将集合中[l..r]范围内的数删去.3.将集合中在[l..r]中的数删去,并将之前不在集合中的数加入集合 考虑到最近线段树总是写爆 ...

  4. CodeForces - 600B Queries about less or equal elements (二分查找 利用stl)

    传送门: http://codeforces.com/problemset/problem/600/B Queries about less or equal elements time limit ...

  5. dp --- Codeforces 245H :Queries for Number of Palindromes

    Queries for Number of Palindromes Problem's Link:   http://codeforces.com/problemset/problem/245/H M ...

  6. codeforces 598B Queries on a String

    题目链接:http://codeforces.com/problemset/problem/598/B 题目分类:字符串 题意:给定一个串,然后n次旋转,每次给l,r,k,表示区间l到r的字符进行k次 ...

  7. Codeforces 245H Queries for Number of Palindromes

    http://codeforces.com/contest/245/problem/H 题意:给定一个字符串,每次给个区间,求区间内有几个回文串(n<=5000) 思路:设定pd[i][j]代表 ...

  8. codeforces:Prefix Sums分析和实现

    题目大意: 给出一个函数P,P接受一个数组A作为参数,并返回一个新的数组B,且B.length = A.length + 1,B[i] = SUM(A[0], ..., A[i]).有一个无穷数组序列 ...

  9. Codeforces 245H Queries for Number of Palindromes:区间dp

    题目链接:http://codeforces.com/problemset/problem/245/H 题意: 给你一个字符串s. 然后有t个询问,每个询问给出x,y,问你区间[x,y]中的回文子串的 ...

随机推荐

  1. 学习笔记20151211——AXI4 STREAM DATA FIFO

    AXI4 STREAM DATA FIFO是输入输出接口均为AXIS接口的数据缓存器,和其他fifo一样是先进先出形式.可以在跨时钟域的应用中用于数据缓冲,避免亚稳态出现.支持数据的分割和数据拼接.在 ...

  2. ISE创建Microblaze软核(二)

    ISE创建Microblaze软核(二) (2012-07-13 15:09:08) 转载▼ 标签: 杂谈 分类: FPGA开发 第四步 进入Platform Studio操作界面 通过向导创建软核后 ...

  3. 通过VNC连接远程服务器,然后登陆服务器上的虚拟机,出现键盘输入问题的解决方法

    前几天由于要在服务器上装一个虚拟机,然后就选择了vmware workstation,装好之后,进入虚拟机中的centOS系统,发现键盘上的Cpas Lock键不起作用,按下之后还是输入小写,而且按住 ...

  4. 关于protel 99se 汉化后某些菜单消失的解决方法

    本人在使用protel 99se 画PCB时,遇到了好些问题,通过网上查资料基本都解决了. 下面给大家分享 关于protel 99se 汉化后某些菜单消失的解决方法. 其他的许多看不见的菜单也可以自己 ...

  5. Leetcode 1023. Camelcase Matching

    暴力查找 class Solution: def camelMatch(self, queries: List[str], pattern: str) -> List[bool]: q_size ...

  6. 利用U盘大白菜软件来重装win7系统

    个人装win7系统用了两个U盘,一个做启动盘(FAT32格式),另外一个当做系统盘(NTFS格式). 首先在电脑里面下载一个大白菜软件,并且安装好,打开软件,插上U盘,检测到了该U盘即可一键制作启动盘 ...

  7. php中require_once与include_once的区别

    首先include_once仅包含文件一次,如果没有文件,会发出警告,并继续执行. 而require_once也是仅包含文件一次,但是如果程序中没有找到文件,则程序会中止执行.

  8. LOJ2316. 「NOIP2017」逛公园【DP】【最短路】【思维】

    LINK 思路 因为我想到的根本不是网上的普遍做法 所以常数出奇的大,而且做法极其暴力 可以形容是带优化的大模拟 进入正题: 首先一个很显然的思路是如果在合法的路径网络里面存在零环是有无数组解的 然后 ...

  9. c++ 声明和定义的区别

    从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存.而定义就是分配了内存. int a;在外面是作为一个语句,这就是定义,会构造对象,定义本身也是声明. ...

  10. cocos2dx 分辨率适配问题

    分辨率适配问题好文章: http://www.cocoachina.com/bbs/read.php?tid=201987&page=1&toread=1#tpc http://www ...