1. 程式人生 > >【動態主席樹】ZOJ 2112【樹狀陣列+主席樹】

【動態主席樹】ZOJ 2112【樹狀陣列+主席樹】

題意:

      給定一個區間,求這個區間第k小的數,支援單點修改。

 

思路:

      動態主席樹裸題。

      我們先來回顧一下靜態主席樹的做法,對於陣列中每一個位置都維護一棵權值線段樹,該權值線段樹儲存的是區間 [1,x] 的資訊。因此我想要求區間 [l,r] 之間第k大的時候,只需要將root[r]-root[l-1]就是維護區間 [l,r] 資訊的權值線段樹,因此就可以快速直接求出這個區間中第k大的元素是多少。

 

      現在我們來看看單點修改的操作。

      如果我現在要將a[pos]修改為x,那麼最暴力的做法就是對於root[pos]~root[n]中的每一顆權值線段樹都進行修改,即將a[pos]這個點的值減1,將x這個點的值+1。

      最暴力的做法顯然是無法通過此題的,因此我們可以想到有沒有一種logn的方法,可以只修改logn個節點,就可以對於每一個線段樹記錄修改資訊,於是我們想到了樹狀陣列。

 

      我們來回憶一下樹狀陣列,每一個節點記錄區間 [x-lowbit(x)+1, x] 的所有資訊,因此當需要求[1,x]內維護的資訊的時候,只需要從節點x出發,每次進行 x-=lowbit(x) 的操作,即可求出[1,x]內維護的所有資訊。

      每次對x節點進行修改的時候,只需要不斷進行x+=lowbit(x)的操作,就可以訪問到所有儲存x節點資訊的節點,因此實現了logn的查詢。

 

      因此本題也維護一個樹狀陣列。

      節點x維護的是區間 [x-lowbit(x)+1, x] 的權值線段樹,因此當我需要訪問 [l, r] 區間資訊的時候,只需要將root[r]-root[l-1]+getsum(r)-getsum(l-1)這裡面維護的便是區間 [l, r] 的權值線段樹。

 

總結:

      樹狀陣列是一種很優秀的思想,他令每一個節點維護一個區間內的資訊,於是在空間複雜度為n的情況下實現了logn的查詢和修改操作,非常偉大!

 

程式碼:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 60010;

int n,m,tot,cnt,idx; //tot記錄節點數 cnt記錄離散化陣列
int a[N],dis[N]; //原陣列 離散化陣列
int root[N],lc[N*40],rc[N*40],sum[N*40]; //root為靜態主席樹的根節點
int s[N],use[N];
//s為動態主席樹的根節點,use是用來儲存更新時,沿著lowbit上升時經過的樹
//s[i]代表一棵權值線段樹,這顆權值線段樹統計的是[i-lowbit(i)+1,i]的區間修改資訊
struct Node{
	int kind;
	int l,r,k;
}que[10010];

int build(int l,int r)
{
	int rt = ++tot;
	sum[rt] = 0;
	if(l != r)
	{
		int mid = (l+r)>>1;
		lc[rt] = build(l,mid);
		rc[rt] = build(mid+1,r);
	}
	return rt;
}

//以last這棵樹為參照,新建一棵樹,即新建的樹有一部分節點共用last這棵樹
int update(int last,int pos,int val)
{
	int rt = ++tot, tmp = rt;
	int l = 1, r = cnt;
	sum[rt] = sum[last]+val;
	while(l < r)
	{
		int mid = (l+r)>>1;
		if(pos <= mid)
		{
			lc[rt] = ++tot, rc[rt] = rc[last]; //對左右兒子賦值
			rt = lc[rt], last = lc[last]; //進入左兒子部分進行更新
			r = mid;
		}
		else
		{
			rc[rt] = ++tot, lc[rt] = lc[last];
			rt = rc[rt], last = rc[last];
			l = mid+1;
		}
		sum[rt] = sum[last]+val;
	}
	return tmp;
}

int lowbit(int x)
{
	return x&(-x);
}

void add(int x,int pos,int val)
{
	while(x <= n)
	{
		s[x] = update(s[x],pos,val);
		x += lowbit(x);
	}
}

int getSum(int x)
{
	int ret = 0;
	while(x > 0)
	{
		ret += sum[lc[use[x]]];
		x -= lowbit(x);
	}
	return ret;
}

int query(int left,int right,int k)
{
	int left_rt = root[left-1];
	int right_rt = root[right];
	int l = 1, r = cnt;
	for(int i = left-1; i ; i-= lowbit(i)) use[i] = s[i];//使用use陣列的目的是將樹狀陣列求和路徑記錄下來
	for(int i = right; i ; i -= lowbit(i)) use[i] = s[i]; //由於是從根節點往下走,一開始是s,後來是lc,所以需要開一個use陣列
	while(l < r) //用迴圈模擬遞迴,減小常數
	{
		int mid = (l+r)>>1;
		int tmp = getSum(right)-getSum(left-1)+sum[lc[right_rt]]-sum[lc[left_rt]];
		if(tmp >= k)
		{
			r = mid;
			for(int i = left-1; i ; i -= lowbit(i)) use[i] = lc[use[i]];
			for(int i = right; i ; i -= lowbit(i)) use[i] = lc[use[i]];
			left_rt = lc[left_rt];
			right_rt = lc[right_rt];
		}
		else{
			l = mid+1;
			k -= tmp;
			for(int i = left-1; i ; i -= lowbit(i)) use[i] = rc[use[i]];
			for(int i = right; i ; i -= lowbit(i)) use[i] = rc[use[i]];
			left_rt = rc[left_rt];
			right_rt = rc[right_rt];
		}
	}
	return l; //此處return l或者r 都是可以的
}

int getID(int x) //二分查詢這個數離散化之後的位置
{
	return lower_bound(dis+1,dis+1+cnt,x)-dis;
}

int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m); //n個人,m條操作
		tot = cnt = idx = 0;
		rep(i,1,n)
		{
			scanf("%d",&a[i]);
			dis[++cnt] = a[i]; //離散化陣列
		}
		char op[10];
		//至於為什麼要先把所有的修改的節點找出來,才進行建樹,那是因為只有知道序列的範圍才可以建樹,無他
		//因此需要離線操作,主要原因是離散化
		rep(i,1,m)
		{
			scanf("%s",op);
			if(op[0] == 'Q')
			{
				que[i].kind = 0;//查詢[l,r]第k大
				scanf("%d%d%d",&que[i].l,&que[i].r,&que[i].k);
			}
			else{
				que[i].kind = 1;//將第x個點改為y
				scanf("%d%d",&que[i].l,&que[i].r);
				dis[++cnt] = que[i].r;
			}
		}
		sort(dis+1,dis+1+cnt);
		cnt = unique(dis+1,dis+1+cnt)-dis-1;//離散化
		root[0] = build(1,cnt);//建立空樹
		rep(i,1,n)
			root[i] = update(root[i-1],getID(a[i]),1);//建立靜態主席樹
		rep(i,1,n)
			s[i] = root[0];//為每個樹狀陣列根節點初始化
		rep(i,1,m)
		{
			if(que[i].kind == 0)
				printf("%d\n",dis[query(que[i].l,que[i].r,que[i].k)]);
			else{
				add(que[i].l,getID(a[que[i].l]),-1);//先消除影響
				add(que[i].l,getID(que[i].r),1);//再新建影響
				a[que[i].l] = que[i].r;
			}
		}
	}
	return 0;
}