[hiho]#1067 : 最近公共祖先·二 离线算法

描述:(原题地址: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;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值