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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值