题目大意:

  提供n个对象,分别编号为1,...,n。每个对象都可能是某个编号小于自己的对象的特例或是成分。认为某个对象的特例的特例依旧是该对象的特例,即特例关系传递,同样一个对象的成分的成分依旧是该对象的成分。但是还需要注意一个对象的成分是该对象的所有特例的成分。每个对象都不可能是自己的特例或成分。要求解答之后的q个谜题,每个谜题提问两个对象是否是特例关系或成分关系。

  输入级别:n,q<1e5


  花了一个晚上才想到思路解了题目。。。

  首先我们要先解决一个简单的问题:对于一株多叉树,如何进行预处理,保证能后续以O(1)的时间复杂度回答任意两个两个结点中是否某个结点存在于另外一个结点的子树中(称这样的关系为衍生关系)。

  。。。

  好,思考时间结束!做法很简单,我们以先序对所有结点进行遍历,同时维护一个整数计数器,每次访问一个结点,就给这个结点分配当前计数器持有的值作为ID,同时令计数器自增。显然每一个结点都会分配到不同的ID值,且任意一个结点的子树中的编号都是连续的,即结点的子树的ID集合为一个正整数区间,而判断另外一个结点是否位于该结点的子树中,只需要判断其ID是否落于后者的子树ID区间中。这样这种衍生关系判断就能在O(1)时间复杂度内解决,而分配ID也只需要O(t)的时间复杂度,t为多叉树中的结点数。

  接下来我们将问题进行修改,假如认为对象的成分不会共享给其特例,即对象的成分不认为是成分的特例,那么如何在O(1)时间复杂度内解决一个谜题?

  很简单,这时候对象的成分关系和特例关系被分离了开来,可以单独计算。这时候可以分别按照成分关系和特例关系建立两株多叉树,并用上面所说的方式进行预处理。这样成分关系和特例关系都可以用衍生关系来表示,即一个对象是另外一个对象的特例,等价于在按特例关系建立的多叉树中前者衍生于后者(即前者落在后者的子树上)。

  到目前一切都很顺利,但是这只是因为没有将成分关系和特例关系混合在一起而已,如果真这么简单就好了。特例关系不受到成分关系的影响,可以直接套用前面的方法进行处理,但是成分关系变得相当复杂。似乎在黑暗中找不到前进的方向一样,困惑了相当久,突然灵光一现:

  是的,这道题目的弱点就在于每个对象或者继承了另外一个对象,或者是另外一个对象的成分,当然也可能二者全无。可以利用这个特性说明下述命题。

  定义:若一个对象是另外一个对象的特例,我们称后者是前者的基。

  定义:若一个对象是另外一个对象的成分,我们称后者是前者的祖先。

  定义:若一个对象没有基,称该对象为基底。

  定义:若一个对象没有祖先,则称该对象为最祖先。

  命题1:对于任意对象A和B,若A是B的成分,则A或者直接是B的成分,或者是B的某个基的成分。

  证明:相当简单的命题,可以由题意直接得到,无需证明。

  命题2:对于任意对象A和B,若A是B的成分,则A或者直接是B的成分,或者B是A的唯一最祖先的特例,或者A是B的唯一基底的成分。

  证明:只需要证明若A是B的某个基的成分等价于“或者B是A的唯一最祖先的特例,或者A是B的唯一基底的成分。”。实际上A若是B的基的成分,只有三种可能,A是B的基底的成分,B是A的最祖先的特例,A是B的某个非基底的基C的成分且C有祖先。但是题目已经说明了一个非基底的对象(即是另外一个对象的特例)是不允许作为其它对象的成分的。因此第三种可能性被删除,只有前面两种可能性保存,命题得证。 

  利用命题2,我们只需要维护每个对象的基底和最祖先,就可以最多通过两次直接的成分关系判断(即在无视特例关系的情况下判断二者是否具有成分关系)和一次特例关系判断来判断对象之间的正确的成分关系。利用前面所述的分配ID的方式可以在O(1)时间复杂度内完成。而维护每个对象的基底和最祖先的工作非常容易,只需要令对象的特例继承该对象的基底,并设置自己的最祖先为某个空对象(或者自己,只需代码逻辑稍微变动),而对象成分关系,令成分继承祖先的最祖先,而自己的基底设置为空对象(或者自己),这些都可以在读取输入的时候附带完成,时间复杂度为O(1)。

  到此,我们预处理的时间复杂度为O(n),后面q次时间复杂度为O(1)的解谜,因此总的时间复杂度为O(n+q),空间复杂度由于维护了树形关系因此为O(n)。


  下面提供代码:

  

 package cn.dalt.codeforces;

 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PushbackInputStream;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;

 /**
  * Created by Administrator on 2017/11/23.
  */
 public class RowenaRavenclawsDiadem {
     private static final String YES = "YES\n";
     private static final String NO = "NO\n";
     int n;
     Node[] nodes;
     AcmInputReader reader;

     public static void main(String[] args) throws IOException {
         RowenaRavenclawsDiadem solution = new RowenaRavenclawsDiadem();
         solution.init();
         String result = solution.solve();
         System.out.println(result);
     }

     public void init() throws IOException {
         reader = new AcmInputReader(System.in);

         n = reader.nextInteger();

         nodes = new Node[n];

         for (int i = 0; i < n; i++) {
             nodes[i] = new Node();

             int parent = reader.nextInteger() - 1;
             int relation = reader.nextInteger();

             switch (relation) {
                 //No relation
                 case -1:
                     nodes[i].ancestor = nodes[i];
                     nodes[i].eldestBrother = nodes[i];
                     break;
                 //Brother
                 case 0:
                     nodes[i].eldestBrother = nodes[parent].eldestBrother;
                     nodes[i].ancestor = nodes[i];
                     nodes[parent].brothers.add(nodes[i]);
                     break;
                 //Parent
                 case 1:
                     nodes[i].eldestBrother = nodes[i];
                     nodes[i].ancestor = nodes[parent].ancestor;
                     nodes[parent].children.add(nodes[i]);
                     break;
             }
         }

         //Allocate the num
         int[] horizontalNum = new int[]{0};
         int[] verticalNum = new int[]{0};
         for (Node node : nodes) {
             horizontalVisit(node, horizontalNum);
             verticalVisit(node, verticalNum);
         }
     }

     public String solve() throws IOException {
         StringBuilder result = new StringBuilder();

         int q = reader.nextInteger();
         for (int i = 0; i < q; i++) {
             int type = reader.nextInteger();
             int u = reader.nextInteger() - 1;
             int v = reader.nextInteger() - 1;

             switch (type) {
                 //Brother test
                 case 1:
                     result.append(u != v && nodes[u].isBrotherOf(nodes[v]) ? YES : NO);
                     break;
                 //Ancestor test
                 case 2:
                     result.append(
                             u != v && !nodes[v].isBrotherOf(nodes[u]) &&
                                     (nodes[v].ancestor == nodes[u]
                                             || nodes[v].ancestor.isBrotherOf(nodes[u])
                                             || nodes[u].eldestBrother.isAncestorOf(nodes[v]))
                                     ? YES : NO
                     );
                     break;
             }
         }

         return result.toString();
     }

     public void horizontalVisit(Node node, int[] horizontalVal) {
         if (node == null || node.horizontalRange.hasBeenInitialized()) {
             return;
         }

         node.horizontalRange.begin = ++horizontalVal[0];
         for (Node brother : node.brothers) {
             horizontalVisit(brother, horizontalVal);
         }
         node.horizontalRange.end = horizontalVal[0];
     }

     public void verticalVisit(Node node, int[] verticalVal) {
         if (node == null || node.verticalRange.hasBeenInitialized()) {
             return;
         }

         node.verticalRange.begin = ++verticalVal[0];
         for (Node child : node.children) {
             verticalVisit(child, verticalVal);
         }
         node.verticalRange.end = verticalVal[0];
     }

     static class Node {
         public Node eldestBrother;
         public Node ancestor;
         public List<Node> brothers = new ArrayList<>();
         public List<Node> children = new ArrayList<>();

         Range horizontalRange = new Range();
         Range verticalRange = new Range();

         public boolean isBrotherOf(Node other) {
             return horizontalRange.contain(other.horizontalRange.begin - 1);
         }

         public boolean isAncestorOf(Node other) {
             return verticalRange.contain(other.verticalRange.begin - 1);
         }
     }

     static class Range {
         int begin = -1;
         int end = -1;

         public boolean hasBeenInitialized() {
             return begin != -1;
         }

         public boolean contain(int other) {
             return other >= begin && other < end;
         }
     }

     /**
      * @author dalt
      * @see java.lang.AutoCloseable
      * @since java1.7
      */
     static class AcmInputReader implements AutoCloseable {
         private PushbackInputStream in;

         /**
          * 创建读取器
          *
          * @param input 输入流
          */
         public AcmInputReader(InputStream input) {
             in = new PushbackInputStream(new BufferedInputStream(input));
         }

         @Override
         public void close() throws IOException {
             in.close();
         }

         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());
         }

         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;
             }
         }
     }
 }

最新文章

  1. jQuery.Callbacks之demo
  2. [Head First设计模式]生活中学设计模式——组合模式
  3. checkbox的check事件
  4. mac上一键配置和安装adb驱动或者环境
  5. iOS开发之 Xcode 5 下让你的应用在不同状态(debug, release)有不同的图标
  6. 用java程序调用ffmpeg执行视频文件格式转换flv
  7. EF 只更新部分字段
  8. POJ1144 Network 无向图的割顶
  9. GLSL实现Ambient Occlusion 【转】
  10. 搜索插件:ack.vim
  11. canvas加载图像
  12. Linux使用小笔记&lt;安装篇&gt;
  13. mysql window版本下载
  14. ISLR系列:(1)线性回归 Linear Regression
  15. MongoDB-BSON
  16. app内嵌vue h5,安卓和ios拦截H5点击事件
  17. kali linux宿主机和虚拟机互访实现方案
  18. 05 方法与数组笔记【JAVA】
  19. mysql中char和varchar详解
  20. 学习:erlang的不定长数据包头部。

热门文章

  1. 在ASP.NET中将GridView数据导出到Word、Excel
  2. avalon 搭配 百度的UI移动框架 gmu 可以很好干活
  3. HTML, CSS. JS的各种奇淫技巧
  4. 多目标跟踪baseline methods
  5. return 0;和exit(0);的区别
  6. python set集合运算(交集,并集,差集,对称差集)
  7. Shell实现Unix进程间信息交换的几种方法
  8. 用T4模版生成对应数据库表的实体类
  9. bzo j4825 [Hnoi2017]单旋
  10. codeforce1029B B. Creating the Contest(简单dp,简单版单调栈)