题目大意:
提供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)。
下面提供代码:
1 package cn.dalt.codeforces; 2 3 import java.io.BufferedInputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.PushbackInputStream; 7 import java.math.BigDecimal; 8 import java.util.ArrayList; 9 import java.util.List; 10 11 /** 12 * Created by Administrator on 2017/11/23. 13 */ 14 public class RowenaRavenclawsDiadem { 15 private static final String YES = "YES\n"; 16 private static final String NO = "NO\n"; 17 int n; 18 Node[] nodes; 19 AcmInputReader reader; 20 21 public static void main(String[] args) throws IOException { 22 RowenaRavenclawsDiadem solution = new RowenaRavenclawsDiadem(); 23 solution.init(); 24 String result = solution.solve(); 25 System.out.println(result); 26 } 27 28 public void init() throws IOException { 29 reader = new AcmInputReader(System.in); 30 31 n = reader.nextInteger(); 32 33 nodes = new Node[n]; 34 35 for (int i = 0; i < n; i++) { 36 nodes[i] = new Node(); 37 38 int parent = reader.nextInteger() - 1; 39 int relation = reader.nextInteger(); 40 41 switch (relation) { 42 //No relation 43 case -1: 44 nodes[i].ancestor = nodes[i]; 45 nodes[i].eldestBrother = nodes[i]; 46 break; 47 //Brother 48 case 0: 49 nodes[i].eldestBrother = nodes[parent].eldestBrother; 50 nodes[i].ancestor = nodes[i]; 51 nodes[parent].brothers.add(nodes[i]); 52 break; 53 //Parent 54 case 1: 55 nodes[i].eldestBrother = nodes[i]; 56 nodes[i].ancestor = nodes[parent].ancestor; 57 nodes[parent].children.add(nodes[i]); 58 break; 59 } 60 } 61 62 //Allocate the num 63 int[] horizontalNum = new int[]{0}; 64 int[] verticalNum = new int[]{0}; 65 for (Node node : nodes) { 66 horizontalVisit(node, horizontalNum); 67 verticalVisit(node, verticalNum); 68 } 69 } 70 71 public String solve() throws IOException { 72 StringBuilder result = new StringBuilder(); 73 74 int q = reader.nextInteger(); 75 for (int i = 0; i < q; i++) { 76 int type = reader.nextInteger(); 77 int u = reader.nextInteger() - 1; 78 int v = reader.nextInteger() - 1; 79 80 81 switch (type) { 82 //Brother test 83 case 1: 84 result.append(u != v && nodes[u].isBrotherOf(nodes[v]) ? YES : NO); 85 break; 86 //Ancestor test 87 case 2: 88 result.append( 89 u != v && !nodes[v].isBrotherOf(nodes[u]) && 90 (nodes[v].ancestor == nodes[u] 91 || nodes[v].ancestor.isBrotherOf(nodes[u]) 92 || nodes[u].eldestBrother.isAncestorOf(nodes[v])) 93 ? YES : NO 94 ); 95 break; 96 } 97 } 98 99 return result.toString(); 100 } 101 102 public void horizontalVisit(Node node, int[] horizontalVal) { 103 if (node == null || node.horizontalRange.hasBeenInitialized()) { 104 return; 105 } 106 107 node.horizontalRange.begin = ++horizontalVal[0]; 108 for (Node brother : node.brothers) { 109 horizontalVisit(brother, horizontalVal); 110 } 111 node.horizontalRange.end = horizontalVal[0]; 112 } 113 114 public void verticalVisit(Node node, int[] verticalVal) { 115 if (node == null || node.verticalRange.hasBeenInitialized()) { 116 return; 117 } 118 119 node.verticalRange.begin = ++verticalVal[0]; 120 for (Node child : node.children) { 121 verticalVisit(child, verticalVal); 122 } 123 node.verticalRange.end = verticalVal[0]; 124 } 125 126 static class Node { 127 public Node eldestBrother; 128 public Node ancestor; 129 public List<Node> brothers = new ArrayList<>(); 130 public List<Node> children = new ArrayList<>(); 131 132 Range horizontalRange = new Range(); 133 Range verticalRange = new Range(); 134 135 public boolean isBrotherOf(Node other) { 136 return horizontalRange.contain(other.horizontalRange.begin - 1); 137 } 138 139 public boolean isAncestorOf(Node other) { 140 return verticalRange.contain(other.verticalRange.begin - 1); 141 } 142 } 143 144 static class Range { 145 int begin = -1; 146 int end = -1; 147 148 public boolean hasBeenInitialized() { 149 return begin != -1; 150 } 151 152 public boolean contain(int other) { 153 return other >= begin && other < end; 154 } 155 } 156 157 158 /** 159 * @author dalt 160 * @see java.lang.AutoCloseable 161 * @since java1.7 162 */ 163 static class AcmInputReader implements AutoCloseable { 164 private PushbackInputStream in; 165 166 /** 167 * 创建读取器 168 * 169 * @param input 输入流 170 */ 171 public AcmInputReader(InputStream input) { 172 in = new PushbackInputStream(new BufferedInputStream(input)); 173 } 174 175 @Override 176 public void close() throws IOException { 177 in.close(); 178 } 179 180 private int nextByte() throws IOException { 181 return in.read() & 0xff; 182 } 183 184 /** 185 * 如果下一个字节为b,则跳过该字节 186 * 187 * @param b 被跳过的字节值 188 * @throws IOException if 输入流读取错误 189 */ 190 public void skipByte(int b) throws IOException { 191 int c; 192 if ((c = nextByte()) != b) { 193 in.unread(c); 194 } 195 } 196 197 /** 198 * 如果后续k个字节均为b,则跳过k个字节。这里{@literal k<times} 199 * 200 * @param b 被跳过的字节值 201 * @param times 跳过次数,-1表示无穷 202 * @throws IOException if 输入流读取错误 203 */ 204 public void skipByte(int b, int times) throws IOException { 205 int c; 206 while ((c = nextByte()) == b && times > 0) { 207 times--; 208 } 209 if (c != b) { 210 in.unread(c); 211 } 212 } 213 214 /** 215 * 类似于{@link #skipByte(int, int)}, 但是会跳过中间出现的空白字符。 216 * 217 * @param b 被跳过的字节值 218 * @param times 跳过次数,-1表示无穷 219 * @throws IOException if 输入流读取错误 220 */ 221 public void skipBlankAndByte(int b, int times) throws IOException { 222 int c; 223 skipBlank(); 224 while ((c = nextByte()) == b && times > 0) { 225 times--; 226 skipBlank(); 227 } 228 if (c != b) { 229 in.unread(c); 230 } 231 } 232 233 /** 234 * 读取下一块不含空白字符的字符块 235 * 236 * @return 下一块不含空白字符的字符块 237 * @throws IOException if 输入流读取错误 238 */ 239 public String nextBlock() throws IOException { 240 skipBlank(); 241 StringBuilder sb = new StringBuilder(); 242 int c = nextByte(); 243 while (AsciiMarksLazyHolder.asciiMarks[c = nextByte()] != AsciiMarksLazyHolder.BLANK_MARK) { 244 sb.append((char) c); 245 } 246 in.unread(c); 247 return sb.toString(); 248 } 249 250 /** 251 * 跳过输入流中后续空白字符 252 * 253 * @throws IOException if 输入流读取错误 254 */ 255 private void skipBlank() throws IOException { 256 int c; 257 while ((c = nextByte()) <= 32) ; 258 in.unread(c); 259 } 260 261 /** 262 * 读取下一个整数(可正可负),这里没有对溢出做判断 263 * 264 * @return 下一个整数值 265 * @throws IOException if 输入流读取错误 266 */ 267 public int nextInteger() throws IOException { 268 skipBlank(); 269 int value = 0; 270 boolean positive = true; 271 int c = nextByte(); 272 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 273 positive = c == ‘+‘; 274 } else { 275 value = ‘0‘ - c; 276 } 277 c = nextByte(); 278 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 279 value = (value << 3) + (value << 1) + ‘0‘ - c; 280 c = nextByte(); 281 } 282 283 in.unread(c); 284 return positive ? -value : value; 285 } 286 287 /** 288 * 判断是否到了文件结尾 289 * 290 * @return true如果到了文件结尾,否则false 291 * @throws IOException if 输入流读取错误 292 */ 293 public boolean isMeetEOF() throws IOException { 294 int c = nextByte(); 295 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { 296 return true; 297 } 298 in.unread(c); 299 return false; 300 } 301 302 /** 303 * 判断是否在跳过空白字符后抵达文件结尾 304 * 305 * @return true如果到了文件结尾,否则false 306 * @throws IOException if 输入流读取错误 307 */ 308 public boolean isMeetBlankAndEOF() throws IOException { 309 skipBlank(); 310 int c = nextByte(); 311 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { 312 return true; 313 } 314 in.unread(c); 315 return false; 316 } 317 318 /** 319 * 获取下一个用英文字母组成的单词 320 * 321 * @return 下一个用英文字母组成的单词 322 */ 323 public String nextWord() throws IOException { 324 StringBuilder sb = new StringBuilder(16); 325 skipBlank(); 326 int c; 327 while ((AsciiMarksLazyHolder.asciiMarks[(c = nextByte())] & AsciiMarksLazyHolder.LETTER_MARK) != 0) { 328 sb.append((char) c); 329 } 330 in.unread(c); 331 return sb.toString(); 332 } 333 334 /** 335 * 读取下一个长整数(可正可负),这里没有对溢出做判断 336 * 337 * @return 下一个长整数值 338 * @throws IOException if 输入流读取错误 339 */ 340 public long nextLong() throws IOException { 341 skipBlank(); 342 long value = 0; 343 boolean positive = true; 344 int c = nextByte(); 345 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 346 positive = c == ‘+‘; 347 } else { 348 value = ‘0‘ - c; 349 } 350 c = nextByte(); 351 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 352 value = (value << 3) + (value << 1) + ‘0‘ - c; 353 c = nextByte(); 354 } 355 in.unread(c); 356 return positive ? -value : value; 357 } 358 359 /** 360 * 读取下一个浮点数(可正可负),浮点数是近似值 361 * 362 * @return 下一个浮点数值 363 * @throws IOException if 输入流读取错误 364 */ 365 public float nextFloat() throws IOException { 366 return (float) nextDouble(); 367 } 368 369 /** 370 * 读取下一个浮点数(可正可负),浮点数是近似值 371 * 372 * @return 下一个浮点数值 373 * @throws IOException if 输入流读取错误 374 */ 375 public double nextDouble() throws IOException { 376 skipBlank(); 377 double value = 0; 378 boolean positive = true; 379 int c = nextByte(); 380 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 381 positive = c == ‘+‘; 382 } else { 383 value = c - ‘0‘; 384 } 385 c = nextByte(); 386 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 387 value = value * 10.0 + c - ‘0‘; 388 c = nextByte(); 389 } 390 391 if (c == ‘.‘) { 392 double littlePart = 0; 393 double base = 1; 394 c = nextByte(); 395 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 396 littlePart = littlePart * 10.0 + c - ‘0‘; 397 base *= 10.0; 398 c = nextByte(); 399 } 400 value += littlePart / base; 401 } 402 in.unread(c); 403 return positive ? value : -value; 404 } 405 406 /** 407 * 读取下一个高精度数值 408 * 409 * @return 下一个高精度数值 410 * @throws IOException if 输入流读取错误 411 */ 412 public BigDecimal nextDecimal() throws IOException { 413 skipBlank(); 414 StringBuilder sb = new StringBuilder(); 415 sb.append((char) nextByte()); 416 int c = nextByte(); 417 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 418 sb.append((char) c); 419 c = nextByte(); 420 } 421 if (c == ‘.‘) { 422 sb.append(‘.‘); 423 c = nextByte(); 424 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 425 sb.append((char) c); 426 c = nextByte(); 427 } 428 } 429 in.unread(c); 430 return new BigDecimal(sb.toString()); 431 } 432 433 private static class AsciiMarksLazyHolder { 434 public static final byte BLANK_MARK = 1; 435 public static final byte SIGN_MARK = 1 << 1; 436 public static final byte NUMERAL_MARK = 1 << 2; 437 public static final byte UPPERCASE_LETTER_MARK = 1 << 3; 438 public static final byte LOWERCASE_LETTER_MARK = 1 << 4; 439 public static final byte LETTER_MARK = UPPERCASE_LETTER_MARK | LOWERCASE_LETTER_MARK; 440 public static final byte EOF = 1 << 5; 441 public static byte[] asciiMarks = new byte[256]; 442 443 static { 444 for (int i = 0; i <= 32; i++) { 445 asciiMarks[i] = BLANK_MARK; 446 } 447 asciiMarks[‘+‘] = SIGN_MARK; 448 asciiMarks[‘-‘] = SIGN_MARK; 449 for (int i = ‘0‘; i <= ‘9‘; i++) { 450 asciiMarks[i] = NUMERAL_MARK; 451 } 452 for (int i = ‘a‘; i <= ‘z‘; i++) { 453 asciiMarks[i] = LOWERCASE_LETTER_MARK; 454 } 455 for (int i = ‘A‘; i <= ‘Z‘; i++) { 456 asciiMarks[i] = UPPERCASE_LETTER_MARK; 457 } 458 asciiMarks[0xff] = EOF; 459 } 460 } 461 } 462 }
codeforces:855D Rowena Ravenclaw's Diadem分析和实现