通过 poj3368 问题讨论:RMQ问题的 tarjan_lca 求解

来源:互联网 发布:无主之地mac下载 编辑:程序博客网 时间:2024/06/06 15:03

首先是tarjan_lca 算法解决 RMQ 问题的思路



RMQ问题:求连续区间内的最大最小值。

我们知道tarjan_lca 是在一棵树上进行的求解(因为是LCA),所以要把整个区间转化成一棵树,对——笛卡尔树

以下引用百度百科对树的定义:
 1、结点一一对应于数列元素。即数列中的每个元素都对应于树中某个唯一结点,树结点也对应于数列中的某个唯一元素
  2、中序遍历(in-order traverse)笛卡尔树即可得到原数列。即任意树结点的左子树结点所对应的数列元素下标比该

结点所对应元素的下标小,右子树结点所对应数列元素下标比该结点所对应元素下标大。
  3、树结构存在堆序性质,即任意树结点所对应数值大/小于其左、右子树内任意结点对应数值

简单地说就是选数列中第一个最小(最大)值点作为根,左右区间分别成为它的左右子树,

并且左右子树都是一棵笛卡树。


百度百科:http://baike.baidu.com/view/6667519.htm(写得有点罗嗦)


如何建立??



我们每次都插入到树的最右边,即根节点的 右子树的 右子树的 右子树…… 

如此,这个点一定符合区间分布规律,接着我们调整它与父亲节点的关系

如果它的值比父亲节点小(大),


1:它没有左子树,那么父亲节点以及父亲的左子树成为它的左子树,父亲右子树为空,它的爷爷成为了它的父亲

2:它有右子树,那么按照上面规则,但是它的左子树链接到父亲的右边

可以证明这个转化是O(N) 

tarjan_lca:



这里我们要用到一个集合set[ i ],用于求解lca

从根开始遍历(dfs),遍历当前节点p 

遍历左子树 把 set [左儿子] = p  

遍历右子树 把 set [右儿子] = p

遍历完后,标记为已访问,开始处理与 p 相关的询问

如果另一个点 y 已经访问了,那么lca就是find( set[ y ] )

这就是tarjan_lca 的全过程……


探讨:

Frequent values
Time Limit: 2000MS Memory Limit: 65536KTotal Submissions: 10390 Accepted: 3811

Description

You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In addition to that, you are given several queries consisting of indices i and j (1 ≤ i ≤ j ≤ n). For each query, determine the most frequent value among the integers ai , ... , aj.

Input

The input consists of several test cases. Each test case starts with a line containing two integers n and q (1 ≤ n, q ≤ 100000). The next line contains n integers a1 , ... , an (-100000 ≤ ai ≤ 100000, for each i ∈ {1, ..., n}) separated by spaces. You can assume that for each i ∈ {1, ..., n-1}: ai ≤ ai+1. The following q lines contain one query each, consisting of two integers i and j (1 ≤ i ≤ j ≤ n), which indicate the boundary indices for the 
query.

The last test case is followed by a line containing a single 0.

Output

For each query, print one line with one integer: The number of occurrences of the most frequent value within the given range.



from:http://poj.org/problem?id=3368


题目有多组数据,而且需要转化成RMQ,然后转化成LCA

不过有一个问题困扰了我很久

AC 485ms

AC 469ms

???~!

明明是O(N) 的算法,但是表现出来的却不是很好,难道读入时间永远是一个痛~~囧


485ms 的程序是我把并查集改成朴素(即不更新集合指向顶元素)后的时间,明显这样查找集合要花费O(N^2)的理论


复杂度,只增加十几毫秒简直匪夷所思啊……继续囧


这是改慢了的并查集:

int find_fa(int p){//int t = p, kp = p;for (; p != father[p]; p = father[p]);/*for (; kp != p; t = kp){kp = father[t];father[t] = p;}*/return p;}
不是明显很慢吗???

为什么测试得到的时间变动不大??

算法的复杂度真的是O(N)??!   

读入输出成为了“核心”??

这些都是我留给大家的问题,希望你们看完后能回复,(*^__^*) ……



以下是poj3368 的程序:(如有类同纯属抄袭)^_^

#include <string.h>#include <stdio.h>struct rec_tree{int up, l, r;rec_tree():up(-1), l(-1), r(-1){}};struct rec_query{int u, qk, e;};const int maxn = 100005;rec_treetree[maxn];rec_queryquery[3*maxn];intans[maxn], head[maxn], a[maxn], nset[maxn], w[maxn], ql[maxn], qr[maxn], father[maxn];int tot, n, m, q, root;boolvis[maxn];void init(){memset(head, -1, sizeof(head));memset(vis, false, sizeof(vis));tot = 0;m = 0;}void add_query(int u1, int v1, int kv){query[tot].u = v1; query[tot].qk = kv; query[tot].e = head[u1]; head[u1] = tot++;query[tot].u = u1; query[tot].qk = kv; query[tot].e = head[v1]; head[v1] = tot++;}void readd(){for (int i = 0; i < n; ++i) scanf("%d", &a[i]);int j, k = 0;while (k < n){j = k;while (j < n && a[j] == a[j+1]) nset[j++] = m;nset[j] = m;w[m++] = j-k+1;k = j+1;}for (int i = 0; i < q; ++i){scanf("%d%d", &ql[i], &qr[i]);if (nset[ql[i]-1]+1 < nset[qr[i]-1]) add_query(nset[ql[i]-1]+1, nset[qr[i]-1]-1, i);}for (int i = 0; i < m; ++i)tree[i] = rec_tree();int l = 0, tp;root = 0;for (int i = 1; i < m; ++i){tree[l].r = i;tree[i].up = l;while (l != -1 && w[i] > w[l]){tp = tree[l].up;tree[i].up = tree[l].up; if (tree[l].up != -1) tree[tree[l].up].r = i;else root = i;tree[l].up = i;if (tree[i].l == -1){tree[i].l = l;tree[l].r = -1;}else {k = tree[i].l; tree[i].l = l;tree[l].r = k; tree[k].up = l;}l = tp;}l = i;}for (int i = 0; i < m; ++i) father[i] = i;}int find_fa(int p){int t = p, kp = p;for (; p != father[p]; p = father[p]);for (; kp != p; t = kp){kp = father[t];father[t] = p;}return p;}void tarjan_lca(int p){if (tree[p].l != -1){tarjan_lca(tree[p].l);father[tree[p].l] = p;}if (tree[p].r != -1){tarjan_lca(tree[p].r);father[tree[p].r] = p;}vis[p] = true;int tp = head[p];while (tp != -1){if (vis[query[tp].u]) ans[query[tp].qk] = w[find_fa(query[tp].u)];tp = query[tp].e;}}int main(){scanf("%d", &n);while (n != 0){scanf("%d", &q);init();readd();tarjan_lca(root);for (int i = 1; i < m; ++i)w[i] += w[i-1];int tm, maxt;for (int i = 0; i < q; ++i){if (nset[ql[i]-1] == nset[qr[i]-1])printf("%d\n", qr[i] - ql[i]+1);else if (nset[ql[i]-1]+1 == nset[qr[i]-1]){maxt = w[nset[ql[i]-1]]-ql[i]+1;if (qr[i]-w[nset[qr[i]-1]-1] > maxt) maxt = qr[i]-w[nset[qr[i]-1]-1];printf("%d\n", maxt);/*std::max(w[nset[ql[i]-1]]-ql[i]+1, qr[i]-w[nset[qr[i]-1]-1]*/}else {tm = w[nset[ql[i]-1]]-ql[i]+1;if (qr[i]-w[nset[qr[i]-1]-1] > tm) tm = qr[i]-w[nset[qr[i]-1]-1];//std::max(w[nset[ql[i]-1]]-ql[i]+1, qr[i]-w[nset[qr[i]-1]-1]);if (ans[i] > tm) tm = ans[i];printf("%d\n", tm);/*std::max(tm, ans[i])*/}}scanf("%d",&n);}}