日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

hdu 2665(主席树查询区间k大值)

發(fā)布時(shí)間:2023/11/27 生活经验 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 hdu 2665(主席树查询区间k大值) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

先貼我自己寫的代碼做模板雖然跟原博主沒什么兩樣。(一開始空間開的4*maxn,交到hdu上一直TLE很奇怪)

#include<bits/stdc++.h>
using namespace std;
const int  maxn=1e5+10;
int rt[20*maxn];
int sum[20*maxn],ls[20*maxn],rs[20*maxn];
int a[maxn],b[maxn];
int cnt;
void build(int& node,int l,int r)
{node=++cnt;sum[node]=0;if(l==r) return ;int mid=(l+r)>>1;build(ls[node],l,mid);build(rs[node],mid+1,r);
}
void update(int& now,int pre,int l,int r,int p)
{now=++cnt;ls[now]=ls[pre];rs[now]=rs[pre];sum[now]=sum[pre]+1;if(l==r) return ;int mid=(l+r)>>1;if(p>mid) update(rs[now],rs[pre],mid+1,r,p);else update(ls[now],ls[pre],l,mid,p);}
int query(int pre,int now,int l,int r,int k)
{if(l==r)return b[l];int mid=(l+r)>>1;int c=sum[ls[now]]-sum[ls[pre]];if(k<=c) return query(ls[pre],ls[now],l,mid,k);else return query(rs[pre],rs[now],mid+1,r,k-c);
}
int main()
{int t,n,m,ql,qr,k;scanf("%d",&t);while(t--){cnt=0;scanf("%d %d",&n,&m);for(int i=1;i<=n;i++){scanf("%d",a+i);b[i]=a[i];}sort(b+1,b+1+n);int len=unique(b+1,b+1+n)-(b+1);build(rt[0],1,len);for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+1+len,a[i])-b;for(int i=1;i<=n;i++)update(rt[i],rt[i-1],1,len,a[i]);for(int i=1;i<=m;i++){scanf("%d %d %d",&ql,&qr,&k);printf("%d\n",query(rt[ql-1],rt[qr],1,len,k));}}
} 

樹狀結(jié)構(gòu)之主席樹

主席樹搞了一個(gè)多星期TAT,,,,,,也只是大致領(lǐng)悟而已!!!

主席樹又稱函數(shù)式線段樹,顧名思義,也就是通過函數(shù)來實(shí)現(xiàn)的線段樹,至于為什么叫主席樹,那是因?yàn)槭莊otile主席創(chuàng)建出來的這個(gè)數(shù)據(jù)結(jié)構(gòu)(其實(shí)貌似是當(dāng)初主席不會劃分樹而自己想出來的另一個(gè)處理方式。。。。是不是很吊呢? ORZ...)不扯了,切入正題。

主席樹就是利用函數(shù)式編程的思想來使線段樹支持詢問歷史版本、同時(shí)充分利用它們之間的共同數(shù)據(jù)來減少時(shí)間和空間消耗的增強(qiáng)版的線段樹。

??? ?很多問題如果用線段樹處理的話需要采用離線思想,若用主席樹則可直接在線處理。故很多時(shí)候離線線段樹求解可以轉(zhuǎn)化為在線主席樹求解。注意,主席樹本質(zhì)就是線段樹,變化就在其實(shí)現(xiàn)可持久化,后一刻可以參考前一刻的狀態(tài),二者共同部分很多。一顆線段樹的節(jié)點(diǎn)維護(hù)的是當(dāng)前節(jié)點(diǎn)對應(yīng)區(qū)間的信息,倘若每次區(qū)間都不一樣,就會給處理帶來一些困難。有時(shí)可以直接細(xì)分區(qū)間然后合并,此種情況線段樹可以直接搞定;但有時(shí)無法通過直接劃分區(qū)間來求解,如頻繁詢問區(qū)間第k小元素,當(dāng)然,此問題有比較特殊的數(shù)據(jù)結(jié)構(gòu)-劃分樹。其實(shí)還有一個(gè)叫做歸并樹,是根據(jù)歸并排序?qū)崿F(xiàn)的,每個(gè)節(jié)點(diǎn)保存的是該區(qū)間歸并排序后的序列,因此,時(shí)間、空間復(fù)雜度都及其高, 所以一般不推薦去用。當(dāng)然,主席樹也是可以解決的。

附上歸并樹代碼:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <vector>
 4 #include <algorithm>
 5 using namespace std;
 6 const int N = 100000 + 5;
 7 
 8 vector<int>node[N << 2];
 9 
10 int T, n, q, ql, qr, ans, k, sz;
11 
12 int a[N], b[N];
13 
14 inline int read(){//快速讀入是邪教
15     char c;
16     int ret = 0;
17     int sgn = 1;
18     do{c = getchar();}while((c < '0' || c > '9') && c != '-');
19     if(c == '-') sgn = -1; else ret = c - '0';
20     while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + (c - '0');
21     return sgn * ret;
22 }
23 
24 void Build(int o, int l, int r){
25     node[o].clear();
26     if(l == r){
27         node[o].push_back(a[l]);
28         return ;
29     }
30     int m = (l + r) >> 1;
31     Build(o << 1, l, m);
32     Build(o << 1|1, m + 1, r);
33     node[o].resize(r - l + 1);
34     merge(node[o<<1].begin(), node[o<<1].end(), node[o<<1|1].begin(), node[o<<1|1].end(), node[o].begin());
35 }
36 
37 int query(int o, int l, int r, int x){
38     //if(ql > r || qr < l) return 0;
39     if(ql <= l && qr >= r)  return upper_bound(node[o].begin(), node[o].end(), x) - node[o].begin();
40     int m = (l + r) >> 1;
41     int ret = 0;
42     if(ql <= m)ret += query(o << 1, l, m, x);
43     if(qr > m)ret += query(o << 1|1, m + 1, r, x);
44     return ret;
45 }
46 
47 void work(){
48     //ql = read();
49     //qr = read();
50     //k = read();
51     scanf("%d%d%d", &ql, &qr, &k);
52     int lt = 1, rt = sz;
53    while(lt <= rt){
54        int md = (lt + rt) >> 1;
55        if(query(1, 1, n, b[md]) >= k)rt = md - 1;
56        else lt = md + 1;
57    } 
58    printf("%d\n", b[rt+1]);
59 }
60 
61 int main(){
62     scanf("%d", &T);
63     while(T--){
64         scanf("%d%d", &n, &q);
65         //n = read();
66         //q = read();
67         //for(int i = 1; i <= n; i ++) a[i] = b[i] = read();
68         for(int i = 1; i <= n; i ++)scanf("%d", a + i), b[i] = a[i];
69         Build(1, 1, n);
70         sort(b + 1, b + n + 1);
71         sz = unique(b + 1, b + n + 1) - (b + 1);
72         while(q --)work();
73     }
74     return 0;
75 }

?

赤果果的嫌棄,時(shí)間居然那么費(fèi),,,,,,,(不過挺好理解的)

??? ?主席樹的每個(gè)節(jié)點(diǎn)對應(yīng)一顆線段樹,此處有點(diǎn)抽象。在我們的印象中,每個(gè)線段樹的節(jié)點(diǎn)維護(hù)的樹左右子樹下標(biāo)以及當(dāng)前節(jié)點(diǎn)對應(yīng)區(qū)間的信息(信息視具體問題定)。對于一個(gè)待處理的序列a[1]、a[2]…a[n],有n個(gè)前綴。每個(gè)前綴可以看做一棵線段樹,共有n棵線段樹;若不采用可持久化結(jié)構(gòu),帶來的嚴(yán)重后果就是會MLE,即對內(nèi)存來說很難承受。根據(jù)可持久化數(shù)據(jù)結(jié)構(gòu)的定義,由于相鄰線段樹即前綴的公共部分很多,可以充分利用,達(dá)到優(yōu)化目的,同時(shí)每棵線段樹還是保留所有的葉節(jié)點(diǎn)只是較之前共用了很多共用節(jié)點(diǎn)。主席樹很重要的操作就是如何尋找公用的節(jié)點(diǎn)信息,這些可能可能出現(xiàn)在根節(jié)點(diǎn)也可能出現(xiàn)在葉節(jié)點(diǎn)。

下面是某大牛的理解:所謂主席樹呢,就是對原來的數(shù)列[1..n]的每一個(gè)前綴[1..i](1≤i≤n)建立一棵線段樹,線段樹的每一個(gè)節(jié)點(diǎn)存某個(gè)前綴[1..i]中屬于區(qū)間[L..R]的數(shù)一共有多少個(gè)(比如根節(jié)點(diǎn)是[1..n],一共i個(gè)數(shù),sum[root] = i;根節(jié)點(diǎn)的左兒子是[1..(L+R)/2],若不大于(L+R)/2的數(shù)有x個(gè),那么sum[root.left] = x)。若要查找[i..j]中第k大數(shù)時(shí),設(shè)某結(jié)點(diǎn)x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在結(jié)點(diǎn)x內(nèi)的數(shù)字總數(shù)。而對每一個(gè)前綴都建一棵樹,會MLE,觀察到每個(gè)[1..i]和[1..i-1]只有一條路是不一樣的,那么其他的結(jié)點(diǎn)只要用回前一棵樹的結(jié)點(diǎn)即可,時(shí)空復(fù)雜度為O(nlogn)。


我自己對主席樹的理解,是一個(gè)線段樹在修改一個(gè)值的時(shí)候,它只要修改logn個(gè)節(jié)點(diǎn)就可以了,那么我們只要每次增加logn個(gè)節(jié)點(diǎn)就可以記錄它原來的狀態(tài)了, 即你在更新一個(gè)值的時(shí)候僅僅只是更新了一條鏈,其他的節(jié)點(diǎn)都相同,即達(dá)到共用。由于主席樹每棵節(jié)點(diǎn)保存的是一顆線段樹,維護(hù)的區(qū)間相同,結(jié)構(gòu)相同,保存的信息不同,因此具有了加減性。(這是主席樹關(guān)鍵所在,當(dāng)除筆者理解了很久很久,才相通的),所以在求區(qū)間的時(shí)候,若要處區(qū)間[l, r], 只需要處理rt[r] - rt[l-1]就可以了,(rt[l-1]處理的是[1,l-1]的數(shù),rt[r]處理的是[1,r]的數(shù),相減即為[l, r]這個(gè)區(qū)間里面的數(shù)。

比如說(以區(qū)間第k大為例hdu2665題目戳這里http://acm.hdu.edu.cn/showproblem.php?pid=2665):

設(shè)n = 4,q= 1;

4個(gè)數(shù)分別為4, 1, 3 ,2;

ql = 1, qr = 3, k = 2;

1.建樹

首先需要建立一棵空的線段樹,也是最原始的主席樹,此時(shí)主席樹只含一個(gè)空節(jié)點(diǎn),此時(shí)設(shè)根節(jié)點(diǎn)為rt[0],表示剛開始的初值狀態(tài),然后依次對原序列按某種順序更新,即將原序列加入到對應(yīng)位置。此過程與線段樹一樣,時(shí)間復(fù)雜度為O(nlogn),空間復(fù)雜度O(nlog(n))(筆者目前沒有完全搞清究竟是多少, 不過保守情況下,線段樹不會超過4*n)

2.更新

?我們知道,更新一個(gè)葉節(jié)點(diǎn)只會影響根節(jié)點(diǎn)到該葉節(jié)點(diǎn)的一條路徑,故只需修改該路徑上的信息即可。每個(gè)主席樹的節(jié)點(diǎn)即每棵線段樹的結(jié)構(gòu)完全相同,只是對應(yīng)信息(可以理解為線段樹的結(jié)構(gòu)完全一樣,只是對應(yīng)葉子節(jié)點(diǎn)取值不同,從而有些節(jié)點(diǎn)的信息不同,本質(zhì)是節(jié)點(diǎn)不同),此時(shí)可以利用歷史狀態(tài),即利用相鄰的上一棵線段樹的信息。相鄰兩顆線段樹只有當(dāng)前待處理的元素不同,其余位置完全一樣。因此,如果待處理的元素進(jìn)入線段樹的左子樹的話,右子樹是完全一樣的,可以共用,即直接讓當(dāng)前線段樹節(jié)點(diǎn)的右子樹指向相鄰的上一棵線段樹的右子樹;若進(jìn)入右子樹,情況可以類比。此過程容易推出時(shí)間復(fù)雜度為O(logn),空間復(fù)雜度為?O(logn)。如圖:

?

3.查詢

先附上處理好之后的主席樹, 如圖:

是不是看著很暈。。。。。。筆者其實(shí)也暈了,我們把共用的節(jié)點(diǎn)拆開來,看下圖:

啊, 這下清爽多了,一眼看下去就知道每個(gè)節(jié)點(diǎn)維護(hù)的是哪棵線段樹了,TAT,如果早就這樣寫估計(jì)很快就明白了,rt[i]表示處理完前i個(gè)數(shù)之后所形成的線段樹,即具有了前綴和的性質(zhì),那么rt[r] - rt[l-1]即表示處理的[l, r]區(qū)間嘍。當(dāng)要查詢區(qū)間[1,3]的時(shí)候,我們只要將rt[3] 和 rt[0]節(jié)點(diǎn)相減即可得到。如圖:

這樣我們得到區(qū)間[l, r]的數(shù)要查詢第k大便很容易了,設(shè)左節(jié)點(diǎn)中存的個(gè)數(shù)為cnt,當(dāng)k<=cnt時(shí),我們直接查詢左兒子中第k小的數(shù)即可,如果k>cnt,我們只要去查右兒子中第k-cnt小的數(shù)即可,這邊是一道很簡單的線段樹了。就如查找[1, 3]的第2小數(shù)(圖上為了方便,重新給節(jié)點(diǎn)標(biāo)號),從根節(jié)點(diǎn)1向下搜,發(fā)現(xiàn)左兒子2的個(gè)數(shù)為1,1<2,所有去右兒子3中搜第2-1級第1小的數(shù),然后再往下搜,發(fā)現(xiàn)左兒子6便可以了,此時(shí)已經(jīng)搜到底端,所以直接返回節(jié)點(diǎn)6維護(hù)的值3即可就可以了。

附上代碼:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int N = 100000 + 5;
 6 
 7 int a[N], b[N], rt[N * 20], ls[N * 20], rs[N * 20], sum[N * 20];
 8 
 9 int n, k, tot, sz, ql, qr, x, q, T;
10 
11 void Build(int& o, int l, int r){
12     o = ++ tot;
13     sum[o] = 0;
14     if(l == r) return;
15     int m = (l + r) >> 1;
16     Build(ls[o], l, m);
17     Build(rs[o], m + 1, r);
18 }
19 
20 void update(int& o, int l, int r, int last, int p){
21     o = ++ tot;
22     ls[o] = ls[last];
23     rs[o] = rs[last];
24     sum[o] = sum[last] + 1;
25     if(l == r) return;
26     int m = (l + r) >> 1;
27     if(p <= m)  update(ls[o], l, m, ls[last], p);
28     else update(rs[o], m + 1, r, rs[last], p);
29 }
30 
31 int query(int ss, int tt, int l, int r, int k){
32     if(l == r) return l;
33     int m = (l + r) >> 1;
34     int cnt = sum[ls[tt]] - sum[ls[ss]];
35     if(k <= cnt) return query(ls[ss], ls[tt], l, m, k);
36     else return query(rs[ss], rs[tt], m + 1, r, k - cnt);
37 }
38 
39 void work(){
40     scanf("%d%d%d", &ql, &qr, &x);
41     int ans = query(rt[ql - 1], rt[qr], 1, sz, x);
42     printf("%d\n", b[ans]);
43 }
44 
45 int main(){
46     scanf("%d", &T);
47     while(T--){
48         scanf("%d%d", &n, &q);
49         for(int i = 1; i <= n; i ++) scanf("%d", a + i), b[i] = a[i];
50         sort(b + 1, b + n + 1);
51         sz = unique(b + 1, b + n + 1) - (b + 1);
52         tot = 0;
53         Build(rt[0],1, sz);
54         //for(int i = 0; i <= 4 * n; i ++)printf("%d,rt =  %d,ls =  %d, rs = %d, sum = %d\n", i, rt[i], ls[i], rs[i], sum[i]);
55         for(int i = 1; i <= n; i ++)a[i] = lower_bound(b + 1, b + sz + 1, a[i]) - b;
56         for(int i = 1; i <= n; i ++)update(rt[i], 1, sz, rt[i - 1], a[i]);
57         for(int i = 0; i <= 5 * n; i ++)printf("%d,rt =  %d,ls =  %d, rs = %d, sum = %d\n", i, rt[i], ls[i], rs[i], sum[i]);
58         while(q --)work();
59     }
60     return 0;
61 }

看著這個(gè)時(shí)間的復(fù)雜度和歸并樹一比,從此對歸并樹無愛了,估計(jì)不會再用了。。。。ORZ~~

4.總結(jié)

由以上可知,主席樹是一種特殊的線段樹集,他幾乎具有所有線段樹的所有優(yōu)勢,并且可以保存歷史狀態(tài),以便以后加以利用,主席樹查找和更新時(shí)時(shí)間空間復(fù)雜度均為O(logn), 且空間復(fù)雜度約為O(nlogn + nlogn)前者為空樹的空間復(fù)雜度,后者為更新n次的空間復(fù)雜度,主席樹的缺點(diǎn)就是空間耗損巨大,但還是可以接受的。當(dāng)然主席樹不止這點(diǎn)應(yīng)用,他可以處理許多區(qū)間問題,例如求區(qū)間[l, r]中的值介于[x,y]的值。總之應(yīng)用多多。

既然要做,那就好好做! 自己選的路,自己走完! 分類:?樹狀結(jié)構(gòu)

總結(jié)

以上是生活随笔為你收集整理的hdu 2665(主席树查询区间k大值)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。