poj 2104 K-th Number && poj 2761 Feed the dogs
http://poj.org/problem?id=2104
http://poj.org/problem?id=2761
这是一道划分树的题目,说白了就是线段树的变种。在百度文库找到相关的文章,我花了整整一个晚上才把这个变种线段树搞懂。虽然划分树只能查询区间K大值,功能局限较大,不过作为一个挺好写的算法,它还是有它的实用价值的!刚开始理解这种数据结构的构造并不难,可是在query的时候会遇到几个相对棘手的问题,这也是相当关键的问题。对着图的划分方法,很容易就想到如何query,不过在这个时候必须要思维清晰,能够深入理解query中每一个值是怎么来的。
在query中,如果要继续划分下去,直到找到的结点是单点,其中的转移是相当容易错的。
我的理解:若k小于等于进入左子树的节点数,那么就要继续访问左子树,不过这时访问的区间就要做出调整,要将有用的部分保留。这个就用到构建树时对进入左子树的结点数的计数了,在我的代码里是用 lf 表示的。进入左子树,找到有用部分起始位置就是要将进入左子树的可是不在查询区间里的那部分舍弃,终止位置就是加上查询区间里进入左子树的结点数。这里注意要调整好起始位置,不然很容易就会数组越界,造成严重错误!相对而言,进入右子树就要将集合取反后再操作。
下面是用递归的方法建树的:
View Code
1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstring> 4 #include <algorithm> 5 6 #define lson l, m, ht + 1 7 #define rson m + 1, r, ht + 1 8 9 using namespace std; 10 11 const int maxn = 100005; 12 int mk[18][maxn], lf[18][maxn], st[maxn]; 13 14 void build(int l, int r, int ht){ 15 if (l == r) return ; 16 17 int m = (l + r) >> 1, p1 = l, p2 = m + 1; 18 19 int rest = m - l + 1; // 记录进入左子树的大小为st[m]的个数 20 for (int i = l; i <= r; i++){ 21 if (mk[ht][i] < st[m]) rest--; 22 } 23 24 for (int i = l; i <= r; i++){ // 将数组分成两边,以st[m]为分界标准 25 if (mk[ht][i] < st[m] || (mk[ht][i] == st[m] && rest)){ 26 mk[ht + 1][p1++] = mk[ht][i]; 27 lf[ht][i] = lf[ht][i - 1] + 1; // 增加进入左子树的结点个数 28 if (mk[ht][i] == st[m]) rest--; 29 } 30 else{ 31 mk[ht + 1][p2++] = mk[ht][i]; 32 lf[ht][i] = lf[ht][i - 1]; 33 } 34 } 35 36 build(lson); 37 build(rson); 38 } 39 40 int query(int s, int t, int k, int l, int r, int ht){ 41 if (s == t) return mk[ht][s]; 42 43 int m = (l + r) >> 1; 44 /******下面是最关键的位置,也是最容易错的地方,稍不留神就会数组越界******/ 45 if (k <= lf[ht][t] - lf[ht][s - 1]){ // 判断在左子树还是在右子树 46 return query(l + lf[ht][s - 1] - lf[ht][l - 1], l + lf[ht][t] - lf[ht][l - 1] - 1, k, lson); 47 } 48 else{ 49 int rs = s - l - (lf[ht][s - 1] - lf[ht][l - 1]); 50 int rt = t + 1 - l - (lf[ht][t] - lf[ht][l - 1]); 51 52 return query(m + 1 + rs, m + rt, k - (lf[ht][t] - lf[ht][s - 1]), rson); 53 } 54 } 55 56 void deal(int n, int m){ 57 for (int i = 1; i <= n; i++){ 58 scanf("%d", &st[i]); 59 mk[0][i] = st[i]; 60 } 61 sort(st + 1, st + n + 1); 62 build(1, n, 0); 63 #ifndef ONLINE_JUDGE 64 for (int i = 0; i <= 3; i++){ 65 printf("%d : \n", i); 66 for (int j = 1; j <= n; j++){ 67 printf("%d ", mk[i][j]); 68 } 69 puts(""); 70 for (int j = 1; j <= n; j++){ 71 printf("%d ", lf[i][j]); 72 } 73 puts(""); 74 } 75 puts("built"); 76 #endif 77 int s, t, k; 78 79 while (m--){ 80 scanf("%d%d%d", &s, &t, &k); 81 printf("%d\n", query(s, t, k, 1, n, 0)); 82 } 83 } 84 85 int main(){ 86 int n, m; 87 88 while (~scanf("%d%d", &n, &m)){ 89 deal(n, m); 90 } 91 92 return 0; 93 }
http://blog.csdn.net/famousdt/article/details/7064866
这个博客的划分树写得挺详细的,我的划分树构造也是参考他的。不过划分树的query是自己yy了一个晚上才搞清楚它的原理的。。。。。
——written by Lyon