區間第k小數問題

2022-03-29 21:42:50 字數 3095 閱讀 9418

原題鏈結

我們在「值域」上建立線段樹。每個節點維護一段值域區間[l,r],並記錄序列中數值落在這段值域區間[l,r]內的點有多少個,記為cnt。

先不考慮下標區間[l,r]的限制。對於詢問整個序列a1~an中的第k小數,我們執行線段樹的查詢操作,對於每個線段樹上的節點,只需比較k與左兒子(其值域區間為[l,mid])的cnt,若k<=l.cnt,那麼就可以遞迴查詢左子樹中的第k小數,否則遞迴查詢右子樹中的第(k-l.cnt)小數。

當然,鑑於ai的規模,我們建立要建立權值線段樹就需要先離散化。

考慮下標區間[l,r]。

顯然,只建立一棵線段樹是不能夠支援我們的操作的。於是我們便需要建立主席樹,考慮將序列a1,a2,a3,……,an依次插入主席樹中,那麼每一棵主席樹的根節點root[i]所對應的其所維護的序列就是a1~ai,以root[i]為根節點的主席樹(值域區間[l,r])就儲存了序列a的前i個數中落在[l,r]內的有多少個。

容易發現一條很重要的性質:由於每棵主席樹中的所有節點所代表的值域區間都是一一對應的,這就意味著: 我們取兩個序列a中的下標l,r。 以root[r]為根的主席樹中儲存了序列a的前r個數中落在[l,r]內的有多少個, 以root[l]為根的主席樹中儲存了序列a的前l個數中落在[l,r]內的有多少個。 所以root[r].cnt-root[l].cnt就是序列a下標區間[l+1,r]中落在值域區間[l,r]內的數有多少個。這樣我們就把下標區間的問題解決了。

對於每次詢問l,r,k。 我們同時遍歷以root[r]為根和以root[l-1]為根的兩棵主席樹,在每乙個點上,計算出兩者左子樹的cnt之差,然後與k作比較,若k較小,則往左遞迴,若k較大,則往右遞迴。就是我們剛才講過的過程了。

於是這個問題就解決了。 當然該問題還有整體二分、歸併樹、線段樹套平衡樹等做法,我們不予討論。、

整個演算法時間複雜度o((n+m)logn),空間複雜度o(nlogn)。

#include #include #include #include using namespace std;

const int n=2e5+10;

struct nodetree[n*22];int idx;

int root[n];

int a[n],b[n];

int n,m,t;

int build(int l,int r)

int insert(int now,int l,int r,int x,int val)

int mid=l+r>>1;

if(x<=mid)l(p)=insert(l(now),l,mid,x,val);

else r(p)=insert(r(now),mid+1,r,x,val);

sum(p)=sum(l(p))+sum(r(p));

return p;

}int query(int u,int v,int l,int r,int k)

int find(int x)

int main()

return 0;

}

原題鏈結

基於我們剛才主席樹的做法,讓以root[i]為根的主席樹維護序列a1~ai的資訊,則當我們要將ax作出修改時,其修改結果會影響到以root[x]、root[x+1]、……、root[n]為根的共n-x+1棵主席樹,如果以樸素的做法,每次都把這些主席樹全部維護的話,時間複雜度是o(nm)的,顯然不行。

但是這也為我們提供了思路。這與字首和十分相似,當我們修改中間某個點時,自然就會對其後所有字首和產生影響。而我們的主席樹並不善於維護字首和,所以我們應該交給擅長維護字首和的樹狀陣列去做。

對於每棵以root[i]為根的子樹,我們讓其維護序列a[i-lowbit(i)+1~i]的資訊。

這樣對於每次單點修改,它最多隻會影響到logn個root,也就是只會影響logn棵主席樹,故我們只需在每次修改時,憑藉樹狀陣列的單點修改操作處理出需要維護哪logn棵主席樹,然後同時進行維護即可。

對於查詢操作也是同樣的,我們通過樹狀陣列的區間查詢操作處理出需要查詢哪logn棵主席樹,然後同時遍歷。

樹狀陣列的查詢操作查詢的就是字首和,對於詢問的區間l,r,我們要分別處理兩組logn棵主席樹,第一組是l-1的字首和,第二組是r的字首和。

然後在遍歷過程中需要與k進行比較然後判斷遞迴左子樹還是右子樹的那一步,我們只需將第二組主席樹的左兒子的cnt求和,減去第一組主席樹的左兒子的cnt之和,再將結果與k進行比較即可。

因為這道題需要離散化,所以我們得離線做。 

整個演算法時間複雜度o(mlog2n),空間複雜度o(nlog2n)。

#include #include #include #include using namespace std;

const int n=1e5+10;

struct quesq[n];

struct nodetree[n*20];int idx;

int root[n];

int a[n],b[n<<1];

int tmp[2][20],cnt[2];

int n,m,t;

int lowbit(int x)

void modify(int &p,int l,int r,int pos,int val)

int mid=l+r>>1;

if(pos<=mid)modify(l(p),l,mid,pos,val);

else modify(r(p),mid+1,r,pos,val);

cnt(p)=cnt(l(p))+cnt(r(p));

}void pre_modify(int x,int val)

int query(int l,int r,int k)

else }

int pre_query(int l,int r,int k)

int main()

else

}sort(b+1,b+t+1);

t=unique(b+1,b+t+1)-(b+1);

for(int i=1;i<=n;i++)pre_modify(i,1);

for(int i=1;i<=m;i++)

else

} return 0;

}

靜態區間 kth 第 k 小數

給定乙個長度為 n 的整數序列,下標為 1 sim n m 個操作,每次給定 l,r,k 表示詢問下標為 l sim r 的區間內第 k 小的數 1 leq n leq 10 1 leq m leq 10 a leq 10 值域很大,建立權值線段樹,權值線段樹要求離散化後的值之間的大小關係不發生改變...

牛客 第k小數 線性尋找第 k 小數

題目大意 給出長度為 n 的數列 a 要求找到第 k 小的數 題目分析 因為資料給的足夠大,所以約束就是必須線性完成操作,stl 中的 nth element 函式可以完美實現操作,算是學到了一波,格式 nth element a.begin a.begin k a.end 那麼 a k 就是第 k...

求第k小數

求第k小數,無非就是考查排序,請參考我最新的博文吧求第k小數 直接使用priority queue,在網上看到許多使用快排來求第k小數都是認為第k小就是排好序陣列中第k個元素,但是如果陣列中有相同的元素呢,例如 1,2,2,2,3,5 認為第三小是3還是2呢?我刷題時遇到的是認為是3。沒有想到什麼好...