描述:(原题地址:http://hihocoder.com/problemset/problem/1067?sid=601284)
给定一颗树,给出树根,以及一些查询pair,要求输出每条查询pair的最近公共节点。
保证所有查询节点都在这棵树上。
输入:第一行一个整数N代表边数,之后N行每行两个节点分别是一对父子,其中第一对父子中的父节点是root。
之后是一个整数M代表查询数,之后M行每行两个节点代表一次查询。1<=N,M<=10^5
输出:对每一个查询输出一行结果。
样例输入:
4 Adam Sam Sam Joey Sam Micheal Adam Kevin 3 Sam Sam Adam Sam Micheal Kevin样例输出:
Sam Adam Adam
其实一开始我是单纯的。。单蠢的以为和在线算法相同,尝试采用标记法对每一个查询只回溯一次,具体做法是这样的:
class Node {
public:
Node *parent;
int flag;
string name;
};
先读入所有父子关系,然后对每一个查询a,b,先让a向根回溯并把所有经过的节点标记为这次查询的序号,然后让b回溯找第一个有记号的节点。
然并卵,一开始用map<string,string>和set<string>直接记录关系和查询路径(5000ms)还是用unordered_map<string,string>加标记法(4000ms)还是用上面这个Node加标记法(3000ms),都是TLE。
无奈地看了提示,才发现原来离线算法是这么做的:
先收集所有查询的信息,然后对树进行一次遍历得到所有查询的结果,然后输出。
这听起来有点不可思议,查询是没有规律的,怎么遍历一次就得到所有结果?做法是将具体的查询分配给对应的节点,然后在遍历过程中让节点负责自己的查询。
使用什么遍历?具有记录祖先特性的遍历就是深度遍历啦。那么遍历到这个节点的时候怎么做呢?依然是标记的方法:一开始所有节点都是白色的,当来到这个节点时,将其标记为灰色;当离开它时,将其标记为黑色。如下图(引自hihoCoder)
此刻我们来到A节点,那么可能有三种查询:AB,AC和AP,分别对应另一个要匹配的节点在自己到root的路径上(正在访问)、已经去过(访问过)和还没经过(未访问过)的节点。
如果是B,很简单直接记录B即可。
如果是P,没法判断,可以将查询交给P,自己不处理。
如果是C,则需要找到一个灰色的节点是C的最近祖先。如果要循着边往上找,比如在查询PA时,让A顺着父节点一直向上找到根,那时间复杂度也是比较大的。不过这可以利用等价类(并查集)算法的技巧,即为每一个节点标记其所属的集合,一开始所有节点都属于自己的集合;当离开一个节点的时候,它要变成黑色了,它的父节点还是灰色,此时将它所属的集合改为其父节点即可。查询的时候遇到黑色的节点,则直接去找其所属集合,如果所属集合为自身则直接返回;‘否则将其沿路所有集合都改成最终所属的集合。还是比如PA,将从A到根的所有节点的所属集合都改成根,以后的查询就可以直接得到结果了。
遍历完成之后,所有查询也完成了,然后依次输出就可以啦。中间忘记进行“将其沿路所有集合都改成最终所属的集合”这一步操作(2000ms)再次导致超时,改正之后(1000ms)就AC了。看起来很复杂,写起来发现其实逻辑理顺了还是比较简洁的。
代码如下:
// Problems.cpp : Defines the entry point for the console application.
//
#include <sstream>
#include <stdio.h>
#include <functional>
#include <string.h>
#include <iomanip>
#include <time.h>
#include <math.h>
#include <fstream>
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#include <tuple>
#include <map>
#include <string>
#include <set>
#include <algorithm>
#include <list>
#include <unordered_set>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int MAXPRIME = 1000000007;
class Node {
public:
static unordered_map<string, Node*> mp;
string name;
char c;
Node *belong;
vector<Node*> children;
unordered_map<string, Node*> query;
Node(string n) : name(n), c(0), belong(this) {}
Node* getBelong() {
if (belong == this) return belong;
belong = belong->getBelong();
return belong;
}
};
unordered_map<string, Node*> Node::mp;
void dfs(Node *root) {
root->c = 1;
// do query
for (auto &i : root->query) {
Node *p = Node::mp[i.first];
if (p->c == 1) { // visiting
i.second = p;
}
else if (p->c == 2) { // visited
i.second = p->getBelong();
}
else { // not visited
p->query[root->name] = NULL; // leave it to the other
}
}
// dfs
for (auto &i : root->children) {
dfs(i);
i->belong = root->belong;
}
root->c = 2;
}
void pro() {
int n;
cin >> n;
string a, b;
Node *root = NULL;
n--;
cin >> a >> b;
root = new Node(a);
Node::mp[a] = root;
Node::mp[b] = new Node(b);
root->children.push_back(Node::mp[b]);
while (n--) {
cin >> a >> b;
if (Node::mp.find(a) == Node::mp.end()) Node::mp[a] = new Node(a);
if (Node::mp.find(b) == Node::mp.end()) Node::mp[b] = new Node(b);
Node::mp[a]->children.push_back(Node::mp[b]);
}
vector<pair<string, string>> q;
cin >> n;
Node *pa, *pb;
while (n--) {
cin >> a >> b;
pa = Node::mp[a], pb = Node::mp[b];
q.push_back(make_pair(a, b));
pa->query[b] = NULL;
}
dfs(root);
for (auto &i : q) {
pa = Node::mp[i.first], pb = Node::mp[i.second];
if (pa->query[i.second]) {
cout << pa->query[i.second]->name << endl;
} else {
cout << pb->query[i.first]->name << endl;
}
}
}
void handle() {
pro();
}
int main() {
handle();
system("PAUSE");
return 0;
}