HYSBZ 1208 宠物收养所 平衡树 set

本文介绍了一种宠物领养系统的实现方法,通过使用不同的数据结构(如set、treap、splay等)来高效处理宠物与领养者之间的匹配过程,并计算领养者的不满意程度总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近,阿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)。【任务描述】你得到了一年当中,领养者和被收养宠物到来收养所的情况,希望你计算所有收养了宠物的领养者的不满意程度的总和。这一年初始时,收养所里面既没有宠物,也没有领养者。

Input

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

Output

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

Sample Input

50 20 41 31 21 5

Sample Output

3(abs(3-2) + abs(2-4)=3,最后一个领养者没有宠物可以领养)

这道题目:给定集合,找某个数的前驱和后继,里面涉及插入,删除,查找,用 STL 的 set 就很方便。

可以发现,宠物和人是可以互换的,分类讨论就可以了。

只要可以高效查找,插入,删除,就可以。

#include <bits/stdc++.h>
using namespace std ;
typedef long long LL ;
const LL INF = 1e12 ;
const int MOD = 1e6 ;
int n , opt , cost ;
LL ans ;

void solve( set<LL> &One , set<LL> &Two ){
    set<LL>::iterator next , pre ;
    if( (int)Two.size() == 2 )
        One.insert( cost ) ;     // 没有人领养
    else{
        pre = Two.lower_bound( cost ) ;
        next = pre-- ;
        if( *pre == -INF || ( *pre != INF  && *next - cost < cost - *pre ) ){
            ans += *next - cost , 
            Two.erase( *next ) ;
        }
        else
            ans += cost - *pre , Two.erase( *pre ) ;
        ans %= MOD ;
    }
}

int main(){
    set<LL> One , Two ;
    One.insert( INF ) , One.insert( -INF ) ;
    Two.insert( INF ) , Two.insert( -INF ) ;
    scanf( "%d" , &n ) ; 
    while( n-- ){
        scanf( "%d %d" , &opt , &cost ) ;  
        if( opt == 0 )             // 新来一个宠物
            solve( One , Two ) ;
        else
            solve( Two , One ) ;   // 新来一个领养者
    }
    cout << ans << endl ;
    return 0 ;
}

也可以用 treap 。我个人觉得 treap 就是二叉搜索树的节点加上一个优先级,通过随机数的作用,使得二叉树整体上是平衡的,因为是随机的嘛 。

treap 编程难度比 AVL 和 红黑树,Splay 树低许多,容易调试,代码容易理解。

#include <bits/stdc++.h>
using namespace std ;
const int MOD = 1e6 ;
int n , opt , cost , ans ;
struct Node{
	int data ;                      // 权重
	int prior ;                     // 优先级
	Node *l , *r ;                  // 左孩子,右孩子
	Node( int _data ){
		data = _data ;  
		prior = rand() ;
		l = r = NULL ;
	}
} ;

void Right( Node *&root ){         // 右转
    Node *temp = root->l ;
    root->l = temp->r ;
    temp->r = root ;
    root = temp ;
}

void Left( Node *&root ){          // 左转
    Node *temp = root->r ;
    root->r = temp->l ;
    temp->l = root ;
    root = temp ;
}

void insert( Node *&root , int data ){
    if( !root ){
        root = new Node( data ) ;
        return ;
    }
    if( data < root->data ){
        insert( root->l , data ) ;
        if( root->l->prior < root->prior )     // 孩子中优先级低的提升
            Right( root ) ;
    }
    else if( data > root->data ){
        insert( root->r , data ) ;
        if( root->r->prior < root->prior )
            Left( root ) ;
    }
}

void erase( Node *&root , int data ){
    if( !root )
        return ;
    if( data < root->data )
        erase( root->l , data ) ;
    else if( data > root->data )
        erase( root->r , data ) ;
    else{
        Node *ptr = root ;
        if( root->l && root->r ){
            if( root->l->prior <= root->r->prior )   // 左孩子优先级更低, 提升
                Right( root ) ,
                erase( root->r , data ) ;
            else
                Left( root ) ,
                erase( root->l , data ) ;
        }
        else{
            root = root->l ? root->l : root->r ;
            delete ptr ; ptr = NULL ;
        }
    }
}

void Get_pre( Node *root , int data , int &ret ) {
    if( !root ) return ;
    if( root->data <= data ) 
        ret = root->data , Get_pre( root->r , data , ret ) ;
    else Get_pre( root->l , data , ret ) ;
}

void Get_nex( Node *root , int data , int &ret ) {
    if( !root ) return ;
    if( root->data >= data ) 
        ret = root->data , Get_nex( root->l , data , ret ) ;
    else Get_nex( root->r , data , ret ) ;
}

void Destroy( Node *&root ) {
    if( root ) 
        Destroy( root->l ) ,
        Destroy( root->r ) ,
        delete root ,
        root = NULL ;
}

void solve( Node *&One , Node *&Two ){
    if( !Two )                         // 找不到匹配的, 先插入
        insert( One , cost ) ;
    else{
        int pre = -1 , next = -1 ;
        Get_pre( Two , cost , pre ) ;      // 找前驱
        Get_nex( Two , cost , next ) ;    // 找后继
        if( next == -1 || ( pre != -1 && cost - pre <= next - cost ) )
            ans += cost - pre ,  
            erase( Two , pre ) ;
        else
            ans += next - cost , 
            erase( Two , next ) ;
        ans %= MOD ;
     }
}

int main() {
    Node *One = NULL , *Two = NULL ;
    scanf( "%d" , &n ) ; 
    while( n-- ){
        scanf( "%d%d" , &opt , &cost ) ;
        if( opt == 0 )                 // 新来一个宠物
            solve( One , Two ) ;
        else                           // 新来一个领养者
            solve( Two , One ) ;
    }
    printf( "%d\n" , ans ) ;
    Destroy( One ) , Destroy( Two ) ;
    return 0 ;
}

最近学了 Splay , Splay 就是每次插入一个数,查找一个数,都先把这个节点提升到根节点,这也就符合了人们对于常用的资料,离根越近,查找越快。删除一个节点,就是先把这个节点提升到根,然后把左右子树合并,在左子树中找最大值,转到根,然后把右子树连到当前根的右边(因为当前根一定没有右孩子)。

Splay 比 Treap 快一点。而且 Splay 还可以做很多区间操作(我还不会 ,只会基本的,emm) 。

#include <bits/stdc++.h>
using namespace std ;
const int MOD = 1e6 ;
int n , opt , cost , ans ;
struct Node {
	int data ;
	Node *fa ;
	Node *ch[2] ;

	int cmp( int x ) const {
		return x == data ? -1 : x < data ? 0 : 1 ;
	}

	Node (int _data , Node *_fa ) {
		data = _data ;
		ch[0] = ch[1] = NULL ;
		fa = _fa ;
	}

	void Destroy( Node *&root ) {
	    if( root ) 
	        Destroy( root->ch[0] ) ,
	        Destroy( root->ch[1] ) ,
	        delete root ,
	        root = NULL ;
	}
} ;

int rson( Node *fa , Node *root ) {                  // 判断  root 是不是  fa 的右孩子,恰好对应 ch[0] , ch[1] 
	return fa ? fa->ch[1] == root : 0 ;
}

struct Splay {
public:
	Splay() { root = NULL ; }
	~Splay() { root->Destroy( root ) ; }
	void insert( int data ) { insert( root , data , NULL ) ; }
	void Get_pre( int data , Node *&ans ) { Get_pre( root , data , ans ) ; }
	void Get_nex( int data , Node *&ans ) { Get_nex( root , data , ans ) ; }

	void Rotate( Node *R ) {        // 旋转 R  的父亲
		Node *fa = R->fa ;
		Node *grand = fa->fa ;
		int d = rson( fa , R ) ;       //  R 是左孩子还是右孩子
		fa->ch[d] = R->ch[d^1] ; 
		if( R->ch[d^1] ) 
			R->ch[d^1]->fa = fa ;  // 链接新的孩子和新的父亲
		R->ch[d^1] = fa ;
		fa->fa = R ;           
		R->fa = grand ;   // 开始连接爷爷
		if( grand ) 
			grand->ch[ rson( grand , fa ) ] = R ; 
		else
			root = R ;
	}

	void splay( Node *t , Node *p ) {   // 把节点 t 提升到 p 的一个孩子,如果 p == NULL , 就是提升到根
		while( t->fa != p ) {
			Node *fa = t->fa ;
			Node *grand = fa->fa ;
			if( grand == p )              // 爷爷就是目标 p ,只要成为爷爷的孩子,需要转一次
				Rotate( t ) ;
			else                          
				if( rson( grand , fa ) ^ rson( fa , t ) ) // 父亲和孩子方位一致
					Rotate( t ) , Rotate( t ) ;
				else
					Rotate( fa ) , Rotate( t ) ;
		}
	}

	void erase( Node *R ) {         // 删除根节点,合并左右孩子
		if( !R ) return ;
		splay( R , NULL ) ;         
		if( R->ch[0] == NULL ) {        // 没有左孩子,右孩子就是新的根
			root = R->ch[1] ;
			if( root )  root->fa = NULL ;
		}
		else {
			Node *p = R->ch[0] ;
			while( p->ch[1] ) 
				p = p->ch[1] ;   // 找左子树最大值
			splay( p , R ) ;         // 把最大值提升称为根
			root = p ;       
			root->fa = NULL ;
			p->ch[1] = R->ch[1] ;    // 新根链接右孩子
			if( p->ch[1] )
				p->ch[1]->fa = p ;
		}
	}

	void Get_pre( Node *R , int data , Node *&ans ) {   // ans 保存前驱
		if( !R ) return ;
		if( data >= R->data )
			ans = R , Get_pre( R->ch[1] , data , ans ) ;
		else Get_pre( R->ch[0] , data , ans ) ;
	}

	void Get_nex( Node *R , int data , Node *&ans ) {   // ans 保存后继
		if( !R ) return ;
		if( data <= R->data )
			ans = R , Get_nex( R->ch[0] , data , ans ) ;
		else Get_nex( R->ch[1] , data , ans ) ;
	}

	bool empty() { return root == NULL ; }

private:
	Node *root ;

	void insert( Node *&root , int data , Node *pre ) {    // 递归插入
		if( !root ) {
			root = new Node( data , pre ) ;
			splay( root , NULL ) ;
			return ;
		}
		int d = root->cmp( data ) ;
		insert( root->ch[d] , data , root ) ;
	}
} ; 

void solve( Splay &One , Splay &Two ){
    if( Two.empty() )                         // 找不到匹配的, 先插入
        One.insert( cost ) ;
    else{
        int pre = -1 , next = -1 ;
        Node *l = NULL , *r = NULL ;
        Two.Get_pre( cost , l ) ;      // 找前驱
        Two.Get_nex( cost , r ) ;      // 找后继
        if( l ) pre = l->data ; 
        if( r ) next = r->data ; 
        if( next == -1 || ( pre != -1 && cost - pre <= next - cost ) )
        	ans += cost - pre ,  
            Two.splay( l , NULL ) ,   // 把 l 转到根,删除
            Two.erase( l ) ;
        else
            ans += next - cost , 
            Two.splay( r , NULL ) ,   // 把 r 转到根,删除
            Two.erase( r ) ;
        ans %= MOD ;
     }
}

int main() {
	Splay One , Two ;
	scanf( "%d" , &n ) ; 
	while( n-- ){
        scanf( "%d%d" , &opt , &cost ) ;
        if( opt == 0 )                 // 新来一个宠物
            solve( One , Two ) ;
        else                           // 新来一个领养者
            solve( Two , One ) ;
    }
	printf( "%d\n" , ans ) ;
	return 0 ;
}

写了一下 AVL 平衡树,终于发现错误了,一直按照以前写的方式,原来我以前写的是错的。比 Splay 慢一点

#include <bits/stdc++.h>
using namespace std ;
const int MOD = 1e6 ;
int n , opt , cost , ans ;
struct Node {
	int data ;
	int depth ;
	Node *ch[2] ;
	Node( int _data ) {
		data = _data ;
		depth = 1 ;
		ch[0] = ch[1] = NULL ;
	}
	int cmp( int x ) const {
		return x == data ? -1 : x < data ? 0 : 1 ;
	}
	int height( Node *root ) {
		return root ? root->depth : 0 ;
	}
	int differ() {
		return height( ch[0] ) - height( ch[1] ) ;
	}
} ;

void update( Node *root ) {
	int l = root->ch[0] ? root->ch[0]->depth : 0 ;
	int r = root->ch[1] ? root->ch[1]->depth : 0 ;
	root->depth = max( l , r ) + 1 ;
}

void Rotate( Node *&root , int d ) {
	Node *t = root->ch[d^1] ;
	root->ch[d^1] = t->ch[d] ;
	t->ch[d] = root ;
	update( root ) ;
	update( t ) ;
	root = t ;
}

void maintain( Node *&root ) {
	int factor = root->differ() ;
	if( factor > 1 ) {
		if( root->ch[0]->differ() > 0 )
			Rotate( root , 1 ) ;
		else
			Rotate( root->ch[0] , 0 ) ,
		    Rotate( root , 1 ) ;
	}
	else if( factor < -1 ) {
		if( root->ch[1]->differ() > 0 )
			Rotate( root->ch[1] , 1 ) ,
		    Rotate( root , 0 ) ;
		else
			Rotate( root , 0 ) ;
	}
}

void insert( Node *&root , int data ) {
	if( !root ) {
		root = new Node( data ) ;
		return ;
	}
	int d = root->cmp( data ) ;
	if( d == -1 ) return ;
	insert( root->ch[d] , data ) ;
	update( root ) ;
	maintain( root ) ;
}

void erase( Node *&root , int data ) {
	if( !root ) return ;
	int d = root->cmp( data ) ;
	if( d == -1 ) {
		if( root->ch[0] && root->ch[1] ) {
			if( root->differ() > 0 ) {
				Node *p = root->ch[0] ;
				while( p->ch[1] )
					p = p->ch[1] ;
				root->data = p->data ;
				erase( root->ch[0] , p->data ) ; 
			}
			else {
				Node *p = root->ch[1] ;
				while( p->ch[0] )
					p = p->ch[0] ;
				root->data = p->data ;
			    erase( root->ch[1] , root->data ) ;
			}
		}
		else {
			Node *t = root ;
			root = root->ch[0] ? root->ch[0] : root->ch[1] ;
			delete t ; t = NULL ;
		}
	} 
	else erase( root->ch[d] , data ) , update( root ) , maintain( root ) ;
}

void Get_pre( Node *root , int data , int &ret ) {
	if( !root ) return ;
	if( data >= root->data )
		ret = root->data , Get_pre( root->ch[1] , data , ret ) ;
	else Get_pre( root->ch[0] , data , ret ) ;
}

void Get_nex( Node *root , int data , int &ret ) {
	if( !root ) return ;
	if( data <= root->data )
		ret = root->data , Get_nex( root->ch[0] , data , ret ) ;
	else Get_nex( root->ch[1] , data , ret ) ;
}

void Destroy( Node *&root ) {
    if( root ) 
        Destroy( root->ch[0] ) ,
        Destroy( root->ch[1] ) ,
        delete root ,
        root = NULL ;
}

void solve( Node *&One , Node *&Two ){
    if( !Two )                         // 找不到匹配的, 先插入
        insert( One , cost ) ;
    else{
        int pre = -1 , next = -1 ;
        Get_pre( Two , cost , pre ) ;      // 找前驱
        Get_nex( Two , cost , next ) ;    // 找后继
        if( next == -1 || ( pre != -1 && cost - pre <= next - cost ) )
        	ans += cost - pre ,  
            erase( Two , pre ) ;
        else
            ans += next - cost , 
            erase( Two , next ) ;
        ans %= MOD ;
     }
}

// #pragma GCC optimize (2)
int main() {
	// freopen( "HYSBZ 1208.txt" , "r" , stdin ) ;
	Node *One = NULL , *Two = NULL ;
	scanf( "%d" , &n ) ; 
	while( n-- ){
        scanf( "%d%d" , &opt , &cost ) ;
        if( opt == 0 )                 // 新来一个宠物
            solve( One , Two ) ;
        else                           // 新来一个领养者
            solve( Two , One ) ;
    }
	printf( "%d\n" , ans ) ;
	Destroy( One ) , Destroy( Two ) ;
	return 0 ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值