poj-2104 K-th Number[主席樹/函數式線段樹/可持久化線段樹]

膜拜大神:點擊打開鏈接 點擊打開鏈接

【題目描述】有n個數字排成一列,有m個詢問,格式爲:left right k .即問在區間[left,right]第k大的數據爲多少?

純屬個人理解,有不正確的地方歡迎留言指正:


先來設想下如何解決這個問題。

把數字在數組中的位置i作爲定義域,數字的值v[i]作爲值域。

假如對於 [left,right]的數我們能知道它們的值域在不同區間出現的個數,就可以根據出現個數來二分查找來找的第k大的值,而區間出現的個數可以用線段樹來存儲。

爲了避免浪費空間,將值域離散化存進線段樹。對於某段值域[l,r],[left,right]中數字出現的個數 =[0,right]中數字出現的個數-[0,left-1]中數字出現的個數

那麼就可以建立 n+1顆線段樹,第i顆線段樹記錄[0,i]數字的值域的區間出現次數。

舉個栗子:

4
10 31 42 15  
這個數據按照上面思路可以建立5個線段樹


很明顯,時間效率和空間效率都很高。

從圖上也能很直觀的看出每個線段樹之間有很多可以共用的節點。

可持久化數據結構

在算法執行的過程中,會發現在更新一個動態集合時,需要維護其過去的版本。這樣的集合稱爲是可持久的。

實現持久集合的一種方法時每當該集合被修改時,就將其整個的複製下來,但是這種方法會降低執行速度並佔用過多的空間。

可持久化數據結構(Persistent data structure)就是利用函數式編程的思想使其支持詢問歷史版本、同時充分利用它們之間的共同數據來減少時間和空間消耗。

單點更新

第i個樹建立的時候,是在第i-1個樹的基礎上把某條從樹根到葉子節點的路徑上的節點+1。這條路徑上的節點是必須新建的,非此路徑的節點可以與上顆樹共用的,與它共用。

這明顯只能用鏈式結構,所以不能對線段樹用標號實現了。


結構

struct node{
	node *ls,*rs;
	int cnt;
};

第0顆樹就只能單獨建立了

node *build(int l,int r)
{//構建第0個線段樹
	node *rt=get_nd();
	if(l==r){
		rt->cnt=0,rt->ls=rt->rs=NULL;
		return rt;
	}
	int mid=(l+r)>>1;
	rt->ls=build(l,mid);
	rt->rs=build(mid+1,r);/
	push_up(rt);
	return rt;
}

構建第1顆到第n顆線段樹

//pos爲第i個數的離散化後的值域,val=1表示增加的個數
//pre是第i-1顆線段樹的指針
node *update(node *pre,int l,int r,int pos,int val)
{//在前一個線段樹的基礎上構建此次線段樹
	node *rt=get_nd();
	*rt=*pre;
	if(l==pos&&r==pos){
		rt->cnt+=val;
		return rt;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) rt->ls=update(pre->ls,l,mid,pos,val);
	else rt->rs=update(pre->rs,mid+1,r,pos,val);
	push_up(rt);
	return rt;
}

查詢第k小個值

int query(node *cur,node *pre,int l,int r,int kth)
{//查詢
	if(l==r) return l;
	int mid=(l+r)>>1;
	int lim=cur->ls->cnt-pre->ls->cnt;
	if(kth<=lim) return query(cur->ls,pre->ls,l,mid,kth);
	else return query(cur->rs,pre->rs,mid+1,r,kth-lim); 
}

poj2104 代碼:

/*【題目描述】有n個數字排成一列,有m個詢問,格式爲:left right k
 即問在區間[left,right]第k大的數據爲多少?*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#define find_max(a,b) a>b?a:b
using namespace std;
const int maxn=100008;
struct node{
	node *ls,*rs;
	int cnt;
}rsta[maxn*30];//內存池
int scnt,n,m;
node *rot[maxn];//第i個線段樹的根節點
int v[maxn];//每個數字的原值
int dv[maxn];//每個數字的離散值
int sub_v[maxn];//v排序後的備份
int maxv;//最大的離散值
inline node *get_nd()
{//從預開的內存池中得到一個節點
	return &rsta[scnt++];
}
inline void push_up(node *rt)
{//上壓
	rt->cnt=rt->ls->cnt+rt->rs->cnt;
}
node *build(int l,int r)
{//構建第0個線段樹
	node *rt=get_nd();
	if(l==r){
		rt->cnt=0,rt->ls=rt->rs=NULL;
		return rt;
	}
	int mid=(l+r)>>1;
	rt->ls=build(l,mid);
	rt->rs=build(mid+1,r);/
	push_up(rt);
	return rt;
}
node *update(node *pre,int l,int r,int pos,int val)
{//在前一個線段樹的基礎上構建此次線段樹
	node *rt=get_nd();
	*rt=*pre;
	if(l==pos&&r==pos){
		rt->cnt+=val;
		return rt;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) rt->ls=update(pre->ls,l,mid,pos,val);
	else rt->rs=update(pre->rs,mid+1,r,pos,val);
	push_up(rt);
	return rt;
}
int query(node *cur,node *pre,int l,int r,int kth)
{//查詢
	if(l==r) return l;
	int mid=(l+r)>>1;
	int lim=cur->ls->cnt-pre->ls->cnt;
	if(kth<=lim) return query(cur->ls,pre->ls,l,mid,kth);
	else return query(cur->rs,pre->rs,mid+1,r,kth-lim); 
}
void discretize()
{//離散化
	for(int i=1;i<=n;++i)
		sub_v[i]=v[i];
	sort(sub_v+1,sub_v+n+1);
	int size=unique(sub_v+1,sub_v+n+1)-sub_v-1;//size爲離散化後元素個數
	for(int i=1;i<=n;++i)//k爲b[i]經離散化後對應的值
	{
		dv[i]=lower_bound(sub_v+1,sub_v+size+1,v[i])-sub_v;
		maxv=find_max(dv[i],maxv);
	}
}
void solve()
{
	scnt=0,maxv=0;
	discretize();
	int t=maxv,r=1;
	while(t) t/=2,r*=2;//求值域範圍
	rot[0]=build(1,r);
	for(int i=1;i<=n;++i)
		rot[i]=update(rot[i-1],1,r,dv[i],1);
	int x,y,k;
	while(m--)
	{
		scanf("%d%d%d",&x,&y,&k);
		k=query(rot[y],rot[x-1],1,r,k);//得到的是離散值
		printf("%d\n",sub_v[k]);
	}
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		for(int i=1;i<=n;++i)
			scanf("%d",v+i);
		solve();
	}
	return 0;
}

發佈了94 篇原創文章 · 獲贊 7 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章