http://acm.hdu.edu.cn/showproblem.php?pid=2665

以前只知道用归并树做,查询时间复杂度要 m * ( log n)^3, 昨天学了一个划分树,查询只要m * logn 。其实这题的正解就是使用划分树来做。划分树跟归并树刚好是相反的操作,划分树查询用于查询[ l , r ]内第k大的数, 归并树用于查询某个数在[ l , r ]内排第几。

划分树的资料比较少,可以参看 小hh 大牛的blog http://www.notonlysuccess.com/?p=142, 或者下面这个blog http://blog.163.com/tonyshaw@yeah/blog/static/142021718201035102322338/

hdu 2665
  1#include <cstdio>
  2#include <iostream>
  3#include <cmath>
  4#include <complex>
  5#include <algorithm>
  6#include <cstring>
  7#include <queue>
  8using namespace std;
  9
 10const int maxn = 100020;
 11int Left[20][maxn], sorted[maxn], tree[20][maxn];
 12
 13void build_tree( int L, int R, int v )
 14{
 15    int mid = ( L + R ) >> 1;
 16    if( L == R ) return;
 17    int m = sorted[mid];
 18    int same = mid - L + 1;     // same表示和m = sorted[mid] 相等且分到左边的
 19    forint i = L; i <= R; i++ )
 20        if( tree[v][i] < m ) same--;
 21    int lpos = L;
 22    int rpos = mid+1;
 23    int ss = 0;
 24    forint i = L; i <= R; i++ )
 25    {
 26        if( i == L ) Left[v][i] = 0;
 27        else Left[v][i] = Left[v][i-1];
 28        if( tree[v][i] < m )
 29        {
 30            tree[v+1][lpos++= tree[v][i];
 31            Left[v][i]++;
 32        }

 33        else if( tree[v][i] > m )
 34        {
 35            tree[v+1][rpos++= tree[v][i];
 36        }

 37        else 
 38        {
 39            if( ss < same )
 40            {
 41                tree[v+1][lpos++= tree[v][i];
 42                Left[v][i]++;
 43                ss++;
 44            }

 45            else tree[v+1][rpos++= tree[v][i];
 46        }

 47    }

 48    build_tree( L, mid, v + 1 );
 49    build_tree( mid + 1, R, v + 1 );
 50}

 51
 52int query( int L, int R, int l, int r, int k, int v )
 53{
 54    int mid = ( L + R ) >> 1;
 55    if( l == r ) return tree[v][l];
 56    int off;      // off表示 [ L, l-1 ]有多少个分到左边
 57    int cnt;      // cnt表示 [ l, r ]有多少个分到左边
 58    if( l == L )
 59    {
 60        off = 0;
 61        cnt = Left[v][r];
 62    }

 63    else
 64    {
 65        off = Left[v][l-1];
 66        cnt = Left[v][r] - Left[v][l-1];
 67    }

 68    if( cnt >= k ) //有多于k个分到左边,显然去左儿子区间找第k个
 69    {
 70        int lnew = L + off;
 71        int rnew = lnew + cnt - 1;
 72        return query( L, mid, lnew, rnew, k, v + 1 );
 73    }

 74    else 
 75    {
 76        off = l - L - off;      // off表示 [ L, l-1 ]有多少个分到右边
 77        k = k - cnt;
 78        cnt = r - l + 1 - cnt;  // cnt表示 [ l, r ]有多少个分到右边
 79        int lnew = mid + 1 + off;
 80        int rnew = lnew + cnt - 1;
 81        return query( mid + 1, R, lnew, rnew, k, v + 1 );
 82    }

 83}

 84
 85int main(int argc, char *argv[])
 86{
 87    int t, n, m, l, r, k;
 88    scanf("%d",&t);
 89    while( t-- )
 90    {
 91        scanf("%d%d",&n,&m);
 92        forint i = 1; i <= n; i++ )
 93        {
 94            scanf("%d",&tree[0][i]);
 95            sorted[i] = tree[0][i];
 96        }

 97        sort( sorted + 1, sorted + n + 1 );
 98        build_tree( 1, n, 0 );
 99        forint i = 0; i < m; i++ )
100        {
101            scanf("%d%d%d",&l,&r,&k);
102            printf("%d\n",query( 1, n, l, r, k, 0 ) );
103        }

104    }

105    return 0;
106}

107