codeforces:855D Rowena Ravenclaw's Diadem分析和实现
题目大意:
提供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;
}
}
}
}
codeforces:855D Rowena Ravenclaw's Diadem分析和实现的更多相关文章
- Codeforces 702F - T-shirts(平衡树+势能分析)
题面传送门 首先肯定将所有物品排个序. 考虑暴力做法,对于每个询问,枚举所有物品,能买就买.不过扫一眼就知道无法直接优化. 不妨换个角度,暴力做法是枚举询问,这次我们枚举物品.从左到右依次枚举所有物品 ...
- codeforces #369div2 B. Chris and Magic Square
题目:在网格某一处填入一个正整数,使得网格每行,每列以及两条主对角线的和都相等 题目链接:http://codeforces.com/contest/711/problem/B 分析:题目不难,找到要 ...
- Amr and Chemistry CodeForces 558C(BFS)
http://codeforces.com/problemset/problem/558/C 分析:将每一个数在给定范围内(10^5)可变成的数(*2或者/2)都按照广搜的方式生成访问一遍,标记上访问 ...
- codeforces MemSQL start[c]up Round 2 - online version B 最长公共子系列
题目链接: http://codeforces.com/contest/335/problem/B 分析: 第一眼看上去串的长度为5*10^4, 冒似只能用O(n)的算法可解. 而这样的算法从来没见 ...
- Codeforces Round #218 (Div. 2)
500pt, 题目链接:http://codeforces.com/problemset/problem/371/A 分析:k-periodic说明每一段长度为k,整个数组被分成这样长度为k的片段,要 ...
- CodeForces 546 D. Soldier and Number Game(素数有关)
Description Two soldiers are playing a game. At the beginning first of them chooses a positive integ ...
- Codeforces 719B Anatoly and Cockroaches
B. Anatoly and Cockroaches time limit per test:1 second memory limit per test:256 megabytes input:st ...
- Codeforces 768B Code For 1
B. Code For 1 time limit per test:2 seconds memory limit per test:256 megabytes input:standard input ...
- Codeforces 777C Alyona and Spreadsheet
C. Alyona and Spreadsheet time limit per test:1 second memory limit per test:256 megabytes input:sta ...
随机推荐
- 在JavaScript中进行文件处理,第二部分:文件读取
译注:原文是<JavaScript高级程序设计>的作者Nicholas Zakas写的,本翻译纯属为自己学习而做,仅供参考.原文链接:这里 在我的前一篇blog中,我介绍了在JavaScr ...
- Python基础学习----名片管理系统学习笔记
主文件: import stu_tools while True: stu_tools.select_list() select = input("请输入您的操作指令") prin ...
- 启动tornado项目,hello world
新建一个env虚拟环境 mkvirtualenv toenv 在虚拟环境中安装tornado workon toenv pip install tornado 在D盘中新建tornado项目文件夹,就 ...
- 单机Hadoop的安装与使用
第一步:安装操作系统并创建Hadoop用户 OS:RHEL6.5 [root@hadoop ~]# useradd hadoop [root@hadoop ~]# passwd hadoop 第二步: ...
- OpenCV - Linux(Ubuntu 16.04)中安装OpenCV + OpenCV_Contrib
近两个月来接触了Linux系统,在老板的建议下翻了Ubuntu的牌子,我安装的版本是16.04,用习惯之后感觉蛮好的,比Windows要强.好啦,废话不说啦,下面开始说在Ubuntu中安装OpemCV ...
- Request.getInputStrema只能读取一次的分析过程
1. 我们先来看一下继承关系HttpServletRequest 接口继承ServletRequest接口 public abstract interface ServletRequest{ pub ...
- streamsets 数据流设计
streamsets 支持branch(分支)&& merge(合并)模式的数据流 branch 数据流 如下图: 我们可以根据数据包含的字段进行拆分,不同的数据流处理自己关注的数据 ...
- oracle12c之 表空间维护总结
1.1.创建永久表空间 In the CDB:SQL> CONNECT system@cdb1SQL> CREATE TABLESPACE cdb_users DATAFILE'/home ...
- swing版网络爬虫-丑牛迷你采集器2.0
swing版网络爬虫-丑牛迷你采集器2.0 http://www.javacoo.com/code/704.jhtml 整合JEECMS http://bbs.jeecms.com/fabu/3186 ...
- 02 - Unit04:笔记本加载功能
@ExceptionHandler 在控制器中统一处理异常. 为了重用异常处理方法,可以将处理方法抽象到父类中,子类共享异常处理方法. 语法: @ExceptionHandler(Exception. ...