天天看点

[hiho]#1069 : 最近公共祖先·三 线段树|树转数组

描述:(原题地址:http://hihocoder.com/problemset/problem/1069?sid=601396)

给定一颗树,给出树根,以及一些查询pair,要求输出每条查询pair的最近公共节点。

保证所有查询节点都在这棵树上。

输入:第一行一个整数N代表边数,之后N行每行两个节点分别是一对父子,其中第一对父子中的父节点是root。

之后是一个整数M代表查询数,之后M行每行两个节点代表一次查询。1<=N,M<=10^5

输出:对每一个查询输出一行结果。

那个,刚写完上一个题,发现这个简直是一毛一样啊,输入输出都没改~这是闹哪样 = =

看看提示吧。。不看不知道,一看吓一跳,居然要在相同复杂度的情况下写成在线版本,也就是一次查询一次输出!

孤陋寡闻的我又可以长长见识了:

小hi这个天才ACMer首先说了一个大家都知道的事情:最近公共祖先就是两个节点的深度最小的公共祖先。简直是废话。然而第二句就让人惊奇:找一个范围内的最小值可以用线段树。等下,线段树我有所耳闻,但是那东西不是需要通过数组来建立的吗?我们现在拿的是一颗树诶。。然后下面的技巧是照搬的了:深度遍历这棵树,在进入这棵树的时候以及从子节点返回这棵树的时候都将这棵树加入到一个数组的结尾,来将一颗树转换为数组。这个数组的大小是原来树的边数的两倍。

好吧确实可以这么转换,而且转换之后只需要找到两个节点最后出现在数组中的位置之间,深度最小的那个节点就是最近公共祖先了!来理一理这句话:两个节点最后出现的位置,也就是它们遍历完自己以及自己所有子树要回到父节点的时刻。由深度遍历的特性,这两个时刻之间遍历的所有节点都属于同一个最近公共祖先(包括这个祖先),并且由于这个最近公共祖先的子节点尚未遍历完,所以一定不会有更高层的祖先。

说明了方法的可行性,接着就完成它吧。分四步:

1. 读入数据,建关系树;这一步比较简单。

2. 深度遍历,树转数组;在深度遍历的时候为每一个节点初始化深度值,并用一个map记录每个节点最后出现的下标。

3. 由数组建线段树;这一步也不复杂,和普通线段树没得差。

4. 查询;也和普通线段树一样。

又是看起来复杂的功能,逻辑分析清楚就很简洁,而且时间复杂度比上次做的离线计算更!低!(250ms)

小hi果然深不可测啊。。。

代码如下:

// 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 TreeNode { // 普通树
public:
  string name;
  int level;
  vector<TreeNode*> children;
  TreeNode(string n) : name(n), level(0) {}
};
class Node { // 线段树
public:
  TreeNode *mnode;
  int rangeleft, rangeright, mid;
  Node *left, *right;
  Node(vector<TreeNode*> &data, int l, int r) :
      mnode(NULL),
      left(NULL),
      right(NULL) {
    mid = rangeleft =l, rangeright = r;
    if (l == r) {
      mnode = data[l];
      return;
    }
    mid = (l + r) >> 1;
    left = new Node(data, l, mid);
    right = new Node(data, mid + 1, r);
    if (left->mnode->level < right->mnode->level) {
      mnode = left->mnode;
    } else {
      mnode = right->mnode;
    }
  }
  TreeNode* find(int l, int r) {
    if (l <= rangeleft && r >= rangeright) {
      return mnode;
    }
    if (r <= mid) {
      return left->find(l, r);
    }
    if (l > mid) {
      return right->find(l, r);
    }
    TreeNode *trl = left->find(l, mid);
    TreeNode *trr = right->find(mid + 1, r);
    return trl->level < trr->level ? trl : trr;
  }
};

// turn a tree into an array
void dfs(vector<TreeNode*> &data, map<string, int> &ind, TreeNode *root, int lv) {
  root->level = lv;
  data.push_back(root);
  ind[root->name] = data.size() - 1;
  for (auto &i : root->children) {
    dfs(data, ind, i, lv + 1);
    data.push_back(root);
    ind[root->name] = data.size() - 1;
  }
}


void pro() {
  int n;
  cin >> n;
  string a, b;
  map<string, int> ind;
  vector<TreeNode*> data;
  map<string, TreeNode*> mp;
  n--;
  cin >> a >> b;
  TreeNode *root = new TreeNode(a);
  mp[a] = root;
  mp[b] = new TreeNode(b);
  root->children.push_back(mp[b]);
  while (n--) {
    cin >> a >> b;
    if (mp.find(a) == mp.end()) mp[a] = new TreeNode(a);
    if (mp.find(b) == mp.end()) mp[b] = new TreeNode(b);
    mp[a]->children.push_back(mp[b]);
  }
  dfs(data, ind, root, 0);
  Node *nd = new Node(data, 0, data.size() - 1);
  cin >> n;
  TreeNode *p;
  while (n--) {
    cin >> a >> b;
    int l = ind[a], r = ind[b];
    if (l > r) swap(l, r);
    p = nd->find(l, r);
    cout << p->name << endl;
  }
}

void handle() {
  pro();
}

int main() {

  handle();

  system("PAUSE");

  return 0;
}