hdu 1540 線段樹

還是要有想象力……

這題需要一個棧,用來保存摧毀的順序。可能出現一個地點,被摧毀多次,而重建時,出棧,只有第一次需要重建,後面再出棧,發現已經是建好的,就不需要再重建

例如:摧毀順序是 5 2 4 5,重建時,5先出棧,重建。然後4、2依次出棧。5再出棧時,發現已經建好,那麼就不用再重建了。

問題的關鍵在如何搜索與某個點直接和間接相連的點,我的思路是:首先遞歸找到這個點,假設是k,然後回退。如果是從r的左孩子退回來的,就去查找r的右孩子是否有斷點(即,r->rt->ok==0),如果有,則肯定是離k點最近,且在k右邊的斷點。同理,如果從r的右孩子退回來,就查找r的左孩子是否有斷點。當然,這個查找,都是第一次出現r->rt->ok==0或者r->lf->ok==0時查找。爲啥必須是第一次?因爲只有第一次時,去查找的斷點纔是離k最近的斷點。這個需要想象哈。

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

typedef struct _Node{
	int ok;
	int size;
	int st, ed;
	struct _Node* lf;
	struct _Node* rt;
}Node, *pNode;

int n, m;
pNode root;

void create(pNode* r, int st, int ed){
	int z;
	pNode w = (pNode)malloc(sizeof(Node));
	*r = w;
	w->st = st; w->ed = ed;
	if(st==ed){
		w->ok = 1;
		w->size = 1;
		w->lf = w->rt = 0;
		return;
	}
	z = (st+ed)>>1;
	create(&(w->lf), st, z);
	create(&(w->rt), z+1, ed);
	w->ok = 1;
	w->size = w->lf->size + w->rt->size;
}

void update(pNode r, int v, int op){ //0, destroy; 1, rebuild;
	int z = (r->st+r->ed)>>1;
	if(r->st==r->ed && r->st==v){
		if(!op){
			r->size = 0;
			r->ok = 0;
		}
		else{
			r->size = 1;
			r->ok = 1;
		}
		return;
	}
	if(v>z)
		update(r->rt, v, op);
	else
		update(r->lf, v, op);
	r->ok = r->lf->ok && r->rt->ok;
	r->size = r->lf->size + r->rt->size;
}

int mkl, mkr, ll, rr;

int find(pNode r, int op){ //0, lf; 1, rt; 用來查找離k最近的斷點。當查找右子樹時,就找最左的點,即爲比k大,且離k最近的點。同理,查找左子樹,就找最右的點,即爲比k小,且離k最近。
	int z = (r->st+r->ed)>>1;
	if(r->st==r->ed) return r->st;
	if(op){
		if(r->lf->ok==0)
			return find(r->lf, op);
		else
			return find(r->rt, op);
	}else{
		if(r->rt->ok==0)
			return find(r->rt, op);
		else
			return find(r->lf, op);
	}
}

void search(pNode r, int v){
	int z = (r->st+r->ed)>>1;
	if(r->st==r->ed && r->st==v){
		if(r->ok==0){
			mkl = mkr = 1;
			ll = rr = r->st;
			rr++;
		}
		return;
	}
	if(v>z){
		search(r->rt, v);
		if(!mkl && !(r->lf->ok)){  //這裏只觸發一次哦。即,從r的右子樹回來,且第一次碰上r的左子樹有斷點,那麼從r的左子樹查找下去,找到的最右邊的斷點,即爲離k最近的斷點!下面同理。
			ll = find(r->lf, 0);
			mkl = 1;
		}
	}else{
		search(r->lf, v);
		if(!mkr && !(r->rt->ok)){
			rr = find(r->rt, 1);
			mkr = 1;
		}
	}
}

void del(pNode r){
	if(!r) return;
	del(r->lf);
	del(r->rt);
	free(r);
}

int destroy[1000];  //棧,用來保存依次摧毀的點
int mk[50001];  //這個是用來標記某點是否被摧毀。0爲未摧毀,1爲已摧毀。
int p;

void main(){
	int i, t;
	char c;
	freopen("in.txt", "r", stdin);
	while(scanf("%d %d", &n, &m)!=EOF){
		create(&root, 1, n);
		p = 0;
		memset(mk, 0, sizeof(mk));
		for(i=0; i<m; i++){
			getchar();
			scanf("%c", &c);
			if(c=='D'){
				scanf("%d", &t);
				if(!mk[t]){  //當重複摧毀時,就不需要再去更新了
					update(root, t, 0);
					mk[t] = 1;
				}
				destroy[p++] = t; //即便多次摧毀不更新,還是要進棧的。都怪題目說的不清楚
			}else if(c=='R'){
				t = destroy[--p];
				if(mk[t]){  //當多次摧毀的點出棧時,就第一次需要重建,後面再出棧,發現已經重建好,就不必再update
					update(root, t, 1);
					mk[t] = 0;
				}
			}else{
				scanf("%d", &t);
				mkl = mkr = 0;
				ll = 0; rr = n+1; //初始化爲最兩端!
				search(root, t);
				printf("%d\n", rr-ll-1);  //最後search後,ll保存的是比t小,且離t最近的斷點。rr保存的是...
			}
		}
	}
}

這只是一種思路,應該有更好的解法。這程序運行,不知怎麼回事,用了28M多的內存,光榮墊底。

補充:忘了加上del了……加上del函數清除內存,再次運行只用3M。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章