「劃分樹」求區(qū)間第k大值

思路:將n個數的序列不斷劃分拓哺,根節(jié)點是原序列勇垛,左子樹是原序列排序后較小的一半,右子樹是另一半士鸥。留意闲孤,子數中的元素的相對位置是和父親序列一樣的,見圖烤礁,這部分參考了這個博客


其中讼积,紅色標記是進入左子樹的元素。

首先脚仔,數組序號1開始數勤众,樹的層數從0開始數。
有2個輔助數組比較關鍵:
sorted[]: 是原始數列排序好的數組鲤脏,用以決定哪些元素要放入左子樹决摧。
toLeft[d][i],表示d層[1,i]下標中有多少個元素被置于左子樹凑兰。以圖的0層為例掌桩,看toLeft[0],那么應該有:toLeft[0]={1姑食,1波岛,1,2音半,2则拷,3,3曹鸠,4}煌茬。

建樹的時候,toLeft[dep][i]=toLeft[dep][l-1]+lpos-l彻桃。含義即dep層至i為止往左子樹的元素數相當于早已前往左子樹的toLeft[dep][l-1]數量坛善,加上這次for循環(huán)新加入的往左的數量。

查詢下標計算比較關鍵。[L,R]是大區(qū)間眠屎,[l,r]是查詢的小區(qū)間剔交,先查詢[l,r]內有前往左子樹的元素數量,記為cnt改衩,若cnt>=k岖常,意味著第k大數必然在左子樹,否則去了右子樹葫督。

不論去哪竭鞍,都要重新計算查詢的區(qū)間。結合圖分析計算


若往左橄镜。新的左起始下標應該是L+toLeft[dep][l-1]-toLeft[dep][L-1]笼蛛,即seg1區(qū)間內往左走的元素,相當于越過seg1前往左子樹元素后蛉鹿,才是[l,r]區(qū)間里往左走的元素起始點滨砍。新的右限自然是newl+cnt-1。

若往右妖异。新的右起始點也應該去除[r, R]區(qū)間往左走的元素干擾惋戏,所以newr=r+toLeft[dep][R]-toleft[dep][r],確定了右限他膳,而[l,r]又往右去了(r-l+1-cnt)個元素(記為t)响逢,那么左起始點就是newr-t+1了。別忘記[l,r]已經有cnt個往左子樹走棕孙,往右則只需找k-cnt大元素即可舔亭。

模板題:poj 2104

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;

#define pii pair<int,int>
#define ll long long
#define mst(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(ll i=(a);i<(b);++i)
const double eps = 1e-8, PI = acos(-1.0f);
const int inf = 0x3f3f3f3f, maxN = 1e5 + 5;
int N, M, T;

int tree[20][maxN];
int sorted[maxN];
int toLeft[20][maxN];

void build(int l, int r, int dep) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    int same = mid - l + 1;
    int smid = sorted[mid];
    rep(i, l, r + 1)
        if (tree[dep][i] < smid)
            --same;

    int lpos = l, rpos = mid + 1;
    rep(i, l, r + 1) {
        if (tree[dep][i] < smid) {
            tree[dep + 1][lpos++] = tree[dep][i];
        } else if (tree[dep][i] == smid && same > 0) {
            --same;
            tree[dep + 1][lpos++] = tree[dep][i];
        } else {
            tree[dep + 1][rpos++] = tree[dep][i];
        }
        toLeft[dep][i] = toLeft[dep][l - 1] + lpos - l;
    }
    build(l, mid, dep + 1);
    build(mid + 1, r, dep + 1);
}

// L R是大區(qū)間 l,r是查詢區(qū)間,查詢第k大值
int query(int L, int R, int l, int r, int dep, int k) {
    if (l == r) return tree[dep][l];
    int mid = (L + R) >> 1;
    int cnt = toLeft[dep][r] - toLeft[dep][l - 1];
    if (cnt >= k) {
        int newl = L + toLeft[dep][l - 1] - toLeft[dep][L - 1];
        int newr = newl + cnt - 1;
        return query(L, mid, newl, newr, dep + 1, k);
    } else {
        int newr = r + toLeft[dep][R] - toLeft[dep][r];
        int newl = newr - (r - l - cnt);
        return query(mid + 1, R, newl, newr, dep + 1, k - cnt);
    }
}

int main() {
    // freopen("data.in", "r", stdin);
    scanf("%d%d", &N, &M);
    rep(i, 1, N + 1) {
        scanf("%d", &tree[0][i]);
        sorted[i] = tree[0][i];
    }
    sort(sorted + 1, sorted + 1 + N);
    build(1, N, 0);
    int s, t, k;
    rep(i, 0, M) {
        scanf("%d%d%d", &s, &t, &k);
        printf("%d\n", query(1, N, s, t, 0, k));
    }
    return 0;
}

hduoj 3473
有一個正整數序列,給定區(qū)間[l,r]蟀俊,找一個整數x使得

最小钦铺,求這個最小值。

首先可以分析得知該x即是[l,r]中的中位數肢预∶矗可以推得
ans=rsum-lsum+midVal*(lcnt-rcnt)。
其中rsum是序列中中位數右側和烫映,lsum對應其左側和沼本,lcnt是左側元素個數,rcnt是右側個數锭沟。
留意:找中位數抽兆,轉右子樹操作,此時的cnt是[l,r]內的位于中位數左側的個數族淮,要和起來辫红,lsum同凭涂。

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

#define pii pair<int,int>
#define ll long long
#define rep(i,a,b) for(ll i=(a);i<(b);++i)
const double eps = 1e-8, PI = acos(-1.0f);
const int inf = 0x3f3f3f3f, maxN = 1e5 + 5;
int N, M, T;
int tree[20][maxN];
int toLeft[20][maxN];
int sorted[maxN];
// leftSum[d][i] 為d層[1,i]進入左子數元素之和
ll leftSum[20][maxN], sum[maxN];
ll lsum, rsum;
int lcnt, rcnt;

void build(int l, int r, int d) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    int same = mid - l + 1;
    int smid = sorted[mid];
    rep(i, l, r + 1)
        if (tree[d][i] < smid)
            --same;

    int lpos = l, rpos = mid + 1;
    rep(i, l, r + 1) {
        if (tree[d][i] < smid) {
            tree[d + 1][lpos++] = tree[d][i];
            leftSum[d][i] = leftSum[d][i - 1] + tree[d][i];
        } else if (tree[d][i] == smid && same > 0) {
            --same;
            tree[d + 1][lpos++] = tree[d][i];
            leftSum[d][i] = leftSum[d][i - 1] + tree[d][i];
        } else {
            tree[d + 1][rpos++] = tree[d][i];
            leftSum[d][i] = leftSum[d][i - 1];
        }
        toLeft[d][i] = toLeft[d][l - 1] + lpos - l;
    }
    build(l, mid, d + 1);
    build(mid + 1, r, d + 1);
}

int query(int L, int R, int l, int r, int d, int k) {
    if (l == r) return tree[d][l];
    
    int mid = (L + R) >> 1;
    int cnt = toLeft[d][r] - toLeft[d][l - 1];
    if (cnt >= k) {
        int newl = L + toLeft[d][l - 1] - toLeft[d][L - 1];
        int newr = newl + cnt - 1;
        return query(L, mid, newl, newr, d + 1, k);
    } else {
        int newr = r + toLeft[d][R] - toLeft[d][r];
        int newl = newr - (r - l - cnt);

        lcnt += cnt;
        lsum += leftSum[d][r] - leftSum[d][l - 1];

        return query(mid + 1, R, newl, newr, d + 1, k - cnt);
    }

}

int main() {
    // freopen("data.in", "r", stdin);
    scanf("%d", &T);
    rep(i, 1, T + 1) {
        sum[0] = 0;

        scanf("%d", &N);
        rep(i, 1, N + 1) {
            scanf("%d", &tree[0][i]);
            sorted[i] = tree[0][i];
            sum[i] = sum[i - 1] + sorted[i];
        }
        sort(sorted + 1, sorted + 1 + N);
        build(1, N, 0);

        printf("Case #%lld:\n", i);
        scanf("%d", &M);
        while (M--) {
            int s, t;
            scanf("%d%d", &s, &t);
            ++s; ++t;
            lcnt = rcnt = 0;
            lsum = 0;
            int mid = (s + t) / 2 - s + 1;
            int midVal = query(1, N, s, t, 0, mid);
            rcnt = t - s + 1 - lcnt;
            rsum = sum[t] - sum[s - 1] - lsum;
            ll ans = rsum - lsum + midVal * (lcnt - rcnt);
            printf("%lld\n", ans);
        }
        puts("");
    }
    return 0;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末较幌,一起剝皮案震驚了整個濱河市揍瑟,隨后出現的幾起案子,更是在濱河造成了極大的恐慌乍炉,老刑警劉巖绢片,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岛琼,死亡現場離奇詭異,居然都是意外死亡槐瑞,警方通過查閱死者的電腦和手機熙涤,發(fā)現死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來困檩,“玉大人祠挫,你說我怎么就攤上這事悼沿。” “怎么了慌植?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵义郑,是天一觀的道長。 經常有香客問我非驮,道長,這世上最難降的妖魔是什么院尔? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任邀摆,我火速辦了婚禮,結果婚禮上栋盹,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好曹仗,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布蠕搜。 她就那樣靜靜地躺著,像睡著了一般轨蛤。 火紅的嫁衣襯著肌膚如雪虫埂。 梳的紋絲不亂的頭發(fā)上祥山,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天缝呕,我揣著相機與錄音斧散,去河邊找鬼岳颇。 笑死颅湘,一個胖子當著我的面吹牛,可吹牛的內容都是我干的瞻鹏。 我是一名探鬼主播新博,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馏慨!你這毒婦竟也來了?” 一聲冷哼從身側響起倔撞,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毫捣,有當地人在樹林里發(fā)現了一具尸體胡本,經...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡披粟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了俺叭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耗跛。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出餐屎,到底是詐尸還是另有隱情空扎,我是刑警寧澤盘寡,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布影涉,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏岔留。R本人自食惡果不足惜里逆,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一诸衔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽离福。三九已至,卻和暖如春妖爷,著一層夾襖步出監(jiān)牢的瞬間绿聘,已是汗流浹背次舌。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人己儒。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像何暇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子裆站,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內容

  • 課程介紹 先修課:概率統(tǒng)計肩袍,程序設計實習,集合論與圖論 后續(xù)課:算法分析與設計氛赐,編譯原理辰妙,操作系統(tǒng)鹰祸,數據庫概論密浑,人...
    ShellyWhen閱讀 2,283評論 0 3
  • 33. Search in Rotated Sorted Array這道題我做了不止一次,附上幾次的代碼: (2)...
    __小赤佬__閱讀 431評論 0 0
  • B樹的定義 一棵m階的B樹滿足下列條件: 樹中每個結點至多有m個孩子街图。 除根結點和葉子結點外,其它每個結點至少有m...
    文檔隨手記閱讀 13,219評論 0 25
  • 前言 這...
    納蘭950316閱讀 307評論 0 0
  • 做了個夢 想睡一位美女 美女沒睡到 卻睡到了愛情 愛情對我說 我想你 我對愛情說 我也想你 我問愛情你在哪里 她說...
    楊孜閱讀 293評論 1 2