「HNOI2004」 宠物收养所 - 平衡树Splay

题目描述

最近,阿Q开了一间宠物收养所。收养所提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物。

每个领养者都希望领养到自己满意的宠物,阿Q根据领养者的要求通过他自己发明的一个特殊的公式,得出该领养者希望领养的宠物的特点值a(a是一个正整数,a<2^31),而他也给每个处在收养所的宠物一个特点值。这样他就能够很方便的处理整个领养宠物的过程了,宠物收养所总是会有两种情况发生:被遗弃的宠物过多或者是想要收养宠物的人太多,而宠物太少。

  1. 被遗弃的宠物过多时,假若到来一个领养者,这个领养者希望领养的宠物的特点值为a,那么它将会领养一只目前未被领养的宠物中特点值最接近a的一只宠物。(任何两只宠物的特点值都不可能是相同的,任何两个领养者的希望领养宠物的特点值也不可能是一样的)如果有两只满足要求的宠物,即存在两只宠物他们的特点值分别为a-b和a+b,那么领养者将会领养特点值为a-b的那只宠物。
  2. 收养宠物的人过多,假若到来一只被收养的宠物,那么哪个领养者能够领养它呢?能够领养它的领养者,是那个希望被领养宠物的特点值最接近该宠物特点值的领养者,如果该宠物的特点值为a,存在两个领养者他们希望领养宠物的特点值分别为a-b和a+b,那么特点值为a-b的那个领养者将成功领养该宠物。

一个领养者领养了一个特点值为a的宠物,而它本身希望领养的宠物的特点值为b,那么这个领养者的不满意程度为abs(a-b)。

你的任务:你得到了一年当中,领养者和被收养宠物到来收养所的情况,希望你计算所有收养了宠物的领养者的不满意程度的总和。这一年初始时,收养所里面既没有宠物,也没有领养者。

输入格式

第一行为一个正整数n,n <= 80000,表示一年当中来到收养所的宠物和领养者的总数。

接下来的n行,按到来时间的先后顺序描述了一年当中来到收养所的宠物和领养者的情况。每行有两个正整数a, b,其中a=0表示宠物,a=1表示领养者,b表示宠物的特点值或是领养者希望领养宠物的特点值。(同一时间呆在收养所中的,要么全是宠物,要么全是领养者,这些宠物和领养者的个数不会超过10000个)

输出格式

仅有一个正整数,表示一年当中所有收养了宠物的领养者的不满意程度的总和mod 1000000以后的结果。

分析

可以很容易看出来,这是一道平衡树的题,就用最常见的Splay来写。一开始想的是开两棵平衡树,分别维护宠物与领养者,虽然可做,但太繁琐。于是就用一棵平衡树在宠物与领养者之间切换。用两个变量来维护宠物和领养者的数量,若读入的为宠物,则检查领养者的数量是否为0,若为0,则将其Insert进平衡树,并将宠物的数量加1;否则说明当前这棵平衡树是领养者的,于是在里面查前驱后继,更新答案,将领养者的数量减1。若读入为领养者类似。这样就可以在宠物与领养者之间来回切换了。

由分析可见,此题用STL set也可以写,且更方便一些。

代码

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int Mod=1000000,N=80008;
const int INF=0x7fffffff/2;
int n,x,p,num[2];
long long ans;
#define lc(x) (a[x].ch[0])
#define rc(x) (a[x].ch[1])
#define fa(x) (a[x].prt)
#define sz(x) (a[x].size)
#define vl(x) (a[x].val)
#define ct(x) (a[x].cnt)
#define nt(x,p) (a[x].ch[p])
#define root a[0].ch[1]
struct BST {
	int ch[2],prt;
	int val,cnt,size;
}a[N];
int tot;
void Updata(int x) {
	sz(x)=sz(lc(x))+sz(rc(x))+ct(x);
}
int Identify(int x) {
	return lc(fa(x))==x?0:1;
}
void Connect(int son,int pt,int lr) {
	fa(son)=pt;
	nt(pt,lr)=son;
}
void Rotate(int x) {
	int y=fa(x);
	int mroot=fa(y);
	int mson=Identify(y);
	int yson=Identify(x);
	int B=nt(x,yson^1);
	Connect(B,y,yson);
	Connect(y,x,yson^1);
	Connect(x,mroot,mson);
	Updata(y);
	Updata(x);
}
void Splay(int at,int to) {
	to=fa(to);
	while (fa(at)!=to) {
		int up=fa(at);
		if (fa(up)==to) Rotate(at);
		else if (Identify(at)==Identify(up)) {
			Rotate(up);
			Rotate(at);
		} else {
			Rotate(at);
			Rotate(at);
		}
	}
}
int Next(int val) {
	int p=root,res=INF,ans=-1;
	while (p) {
		if (vl(p)==val) {
			res=vl(p);
			ans=p;
			break;
		}
		if (vl(p)>val&&vl(p)<res) {
			res=vl(p);
			ans=p;
		}
		if (val<vl(p)) p=lc(p);
		else p=rc(p);
	}
	if (ans!=-1) Splay(ans,root);
	return ans;
}
int Pre(int val) {
	int p=root,res=-INF,ans=-1;
	while (p) {
		if (vl(p)==val) {
			res=vl(p);
			ans=p;
			break;
		}
		if (vl(p)<val&&vl(p)>res) {
			res=vl(p);
			ans=p;
		}
		if (val<vl(p)) p=lc(p);
		else p=rc(p);
	}
	if (ans!=-1) Splay(ans,root);
	return ans;
}
int New(int val,int prt) {
	tot++;
	fa(tot)=prt;
	vl(tot)=val;
	sz(tot)=ct(tot)=1;
	return tot;
}
void Insert(int val) {
	if (tot==0) {
		root=New(val,0);
		return;
	}
	int p=root,nxt;
	while (1) {
		sz(p)++;
		if (vl(p)==val) {
			ct(p)++;
			Splay(p,root);
			return;
		}
		nxt=(val<vl(p)?0:1);
		if (!nt(p,nxt)) {
			int t=New(val,p);
			nt(p,nxt)=t;
			Splay(t,root);
			return;
		}
		p=nt(p,nxt);
	}
}
int Find(int val) {
	int p=root,nxt;
	while (p) {
		if (vl(p)==val) return p;
		nxt=(val<vl(p)?0:1);
		p=nt(p,nxt);
	}
	return -1;
}
void Remove(int val) {
	int p=Find(val);
	if (p==-1) return;
	if (ct(p)>1) {
		sz(p)--;
		ct(p)--;
		return;
	}
	Splay(p,root);
	int l=lc(p),r=rc(p);
	if (!l) {
		root=r;
		fa(root)=0;
		return;
	}
	while (rc(l)) l=rc(l);
	Splay(l,lc(root));
	Connect(r,l,1);
	Connect(l,0,1);
	Updata(l);
}
int main() {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		scanf("%d%d",&p,&x);
		if (!num[p^1]) {
			Insert(x);
			num[p]++;
		} else {
			int t1=Pre(x),t2=Next(x),v1=INF,v2=INF;
			if (t1!=-1) v1=x-vl(t1);
			if (t2!=-1) v2=vl(t2)-x;
			if (v1<=v2) {
				ans+=v1;
				Remove(vl(t1));
			} else {
				ans+=v2;
				Remove(vl(t2));
			}
			num[p^1]--;
		}
		ans=ans%Mod;
	}
	printf("%lld",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值