hdu 3487

做了一天……内牛满面……哭

参考资料:

http://dongxicheng.org/structure/splay-tree/

http://blog.csdn.net/liuwei_nefu/article/details/5901869


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

typedef struct _Node{
	int val;
	int rev;
	int child;
	struct _Node* pre;
	struct _Node* ch[2];  //0,左孩子; 1,右孩子;
}Node, *pNode;

int n, m;
pNode root;

/*更新节点的孩子数*/
void update(pNode r){
	int lf, rt;
	lf = r->ch[0]?(r->ch[0]->child+1):0;
	rt = r->ch[1]?(r->ch[1]->child+1):0;
	r->child = lf+rt;
}

// void insert(pNode* r, int data){
// 	pNode w = *r;
// 	if(!w){
// 		w = (pNode)malloc(sizeof(Node));
// 		w->val = data;
// 		w->child = 0;
// 		w->rev = 0;
// 		w->pre = 0;
// 		w->ch[0] = w->ch[1] = 0;
// 		*r = w;
// 		return;
// 	}
// 	if(data > w->val){
// 		insert(&(w->ch[1]), data);
// 		if(w->ch[1]->child==0) w->ch[1]->pre = w;
// 	}else{
// 		insert(&(w->ch[0]), data);
// 		if(w->ch[0]->child==0) w->ch[0]->pre = w;
// 	}
// 	update(w);
// }

/*旋转,0为往左旋转,1为往右旋转。旋转要注意各种指针的更新,要仔细啊!*/
void rotate(pNode* r, int op){ //0,lf; 1,rt;
	pNode t, w = *r;
	t = w->ch[!op];  //获得左/右孩子
	w->ch[!op] = t->ch[op]; 
	if(t->ch[op]) t->ch[op]->pre = w;
	t->ch[op] = w;
	t->pre = w->pre;
	if(w->pre){  //更新w父节点的孩子指针
		if(w == w->pre->ch[0])
			w->pre->ch[0] = t;
		else
			w->pre->ch[1] = t;
	}else
		root = t;  //这句非常重要!!!如果t旋转到了最顶点,即w->pre为0,那么要更新root指向!
	w->pre = t;
	update(w);
	update(t);
	*r = t;  //将新的节点赋值到*r中
}

/*如果r被标记,则旋转r的“直接”子树,并将标记下传到“直接”孩子*/
void push(pNode r){
	pNode t;
	if(r && r->rev){
		t = r->ch[0];
		r->ch[0] = r->ch[1];
		r->ch[1] = t;

		r->rev = 0;
		if(r->ch[0])
			r->ch[0]->rev ^= 1;
		if(r->ch[1])
			r->ch[1]->rev ^= 1;
	}
}

/*寻找中序遍历的第k个节点*/
pNode find(int k, pNode r){
	int t;
	if(!r) return 0;
	push(r);
	t = r->ch[0]?(r->ch[0]->child+1):0;
	if(k==t+1)
		return r;
	if(k>t+1)
		return find(k-t-1, r->ch[1]);
	else
		return find(k, r->ch[0]);
}

/*伸展运动,将a旋转到b的下面。当b为0时,即,将a旋转为root*/
void splay(pNode a, pNode b){
	pNode k;
	k = a->pre;
	if(!k || k==b)
		return;

	if(a==k->ch[0])
		rotate(&k, 1);
	else
		rotate(&k, 0);
	splay(k, b);
}

// void show(pNode r){
// 	if(!r) return;
// 	show(r->ch[0]);
// 	printf("%d ", r->val);
// 	show(r->ch[1]);
// }

/*删除树,养成好习惯。不过要稍微多耗一点时间*/
void del(pNode r){
	if(!r) return;
	del(r->ch[0]);
	del(r->ch[1]);
	free(r);
}

/*将a~b切下来,放到c的后面*/
void cut(){
	int a, b, c;
	pNode tmp;
	scanf("%d %d %d", &a, &b, &c);
	splay(find(a, root), 0);  //将实际的第a-1个节点旋转为root
	splay(find(b+2, root), root); //将实际的第b+1个节点旋转为root的下面,即为root的右孩子(因为a<=b),这样在a-1与b+1之间,即为a~b,也就是b+1节点的左孩子!
	tmp = root->ch[1]->ch[0]; //将b+1节点的左孩子切下来!
	root->ch[1]->ch[0] = 0; 
	update(root->ch[1]); //更新切下后的节点
	update(root);

	splay(find(c+1, root), 0); //将实际的第c个节点旋转为root
	splay(find(c+2, root), root);  //将实际的第c+1个节点旋转到root,即,第c个节点的下方。这时,第c+1个节点的左孩子肯定为空!因为比c+1小1的c,已经为root了!
	root->ch[1]->ch[0] = tmp;  //将前面切下的接到第c+1个节点的左孩子
	tmp->pre = root->ch[1];
	update(root->ch[1]);  //更新接上节点后的节点
	update(root);
}

/*反转!*/
void rvs(){
	int a, b;
	scanf("%d %d", &a, &b);
	splay(find(a, root), 0); //旋转为root
	splay(find(b+2, root), root); //旋转到root的下面,那么a-1与b+1之间,即b+1节点的左孩子,就是a~b
	root->ch[1]->ch[0]->rev ^= 1;  //只将b+1节点的直接左孩子标记即可,除非被下次访问到,否则不需要马上反转!请看参考资料。
}

int mk;

void out(pNode r){
	if(!r) return;
	push(r);
	out(r->ch[0]);
	if(r->val>0 && r->val<n+1){
		if(mk)
			mk = 0;
		else
			printf(" ");
		printf("%d", r->val);
	}
	out(r->ch[1]);
}
/*使用二分初始化树,这样可以使树高度最低!如果采用直接右插,建好的树深度为n+2,会爆栈溢出!*/
void init(pNode* r, int st, int ed, pNode pre){
	pNode w;
	int m = (st+ed)>>1;
	if(st>ed) return;

	w = (pNode)malloc(sizeof(Node));
	w->val = m;
	w->pre = pre;
	w->rev = 0;
	w->child = ed-st;
	w->ch[0] = w->ch[1] = 0;
	*r = w;
	
	init(&(w->ch[0]), st, m-1, w);
	init(&(w->ch[1]), m+1, ed, w);
}

void main(){
	char opt[5];
	int i;
	freopen("in.txt", "r", stdin);
	while(scanf("%d %d", &n, &m), !(n<0&&m<0)){
		root = 0;
		init(&root, 0, n+1, 0);  //注意啦!这里插入的是0~n+1,共n+2个节点!为啥呢?是为了方便cut操作!所以如果要找第a个节点,实际要find第a+1个
		for(i=0; i<m; i++){
			getchar();
			scanf("%s", opt);
			if(opt[0]=='C')
				cut();
			else
				rvs();
		}
		mk = 1;
		out(root);
		printf("\n");
		del(root);
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值