平衡树习题题解

Luogu P1110

题目描述

小 Q 的妈妈是一个出纳,经常需要做一些统计报表的工作。今天是妈妈的生日,小 Q 希望可以帮妈妈分担一些工作,作为她的生日礼物之一。

经过仔细观察,小 Q 发现统计一张报表实际上是维护一个非负整数数列,并且进行一些查询操作。

在最开始的时候,有一个长度为 n 的整数序列 a,并且有以下三种操作:

INSERT i k:在原数列的第 i 个元素后面添加一个新元素 k;如果原数列的第 i 个元素已经添加了若干元素,则添加在这些元素的最后。

MIN_GAP:查询相邻两个元素的之间差值(绝对值)的最小值。

MIN_SORT_GAP:查询所有元素中最接近的两个元素的差值(绝对值)。

于是小 Q 写了一个程序,使得程序可以自动完成这些操作,但是他发现对于一些大的报表他的程序运行得很慢,你能帮助他改进程序么?

题目分析

首先题面描述非常的简洁直白,我们再简化一下题意。题意还不够简洁吗

给定长度为 n 的整数序列 a ,要求支持以下三种操作:

1.在第 i 个元素后面添加一个新元素 k;如果原数列的第 i 个元素已经添加了若干元素,则添加在这些元素的最后。

2.查询相邻两个元素的之间差值(绝对值)的最小值

3.查询所有元素中最接近的两个元素的差值(绝对值)

我们来分析一下这3种操作:

首先对于操作1,我们不难想到用链表或者vector存储,因为每个元素相当于是独立的,我们就算在其中一个元素后面插入一大串数也不会影响其他的元素,类似于图的vector存储方式。

然后我们再考虑操作2。在不考虑插入修改的情况下操作2很容易解决,所以我们可以预先处理好答案,再考虑插入修改。我们不难发现每次插入 k 时,只会影响 k 的前后两个数的差值大小。

具体地,我们应该删除掉原来两个元素之间的差值,而加入k分别与其前后两个数的差值,然后再修改答案。

我们不难发现其中出现了插入删除操作,所以很自然地就能想到平衡树啦。考虑到我们是在对最终答案进行修改,所以我们不妨直接用平衡树维护答案的插入和删除操作。

最后对于操作3,其主要思路类似于操作2,我们先预处理出原数组最接近的两个数的差值,再在每次插入时更新答案。因为每次插入时只有k和其前驱后继的差值能影响答案,所以还是用平衡树维护所有数据,在插入时查找k的前驱后继,更新答案。

所以整个题目的大体思路就有啦!

实现思路

操作一

我们可以用数组模拟链表,考虑到只有一个元素的最后一个插入的数是有用的,我们仅记录每个元素最后插入的数字即可。或者是考虑用vector实现。

操作二

我们先预处理出原数组的所有的相邻差值答案,然后我们将其全部插入平衡树。这时我们不难看出,只要一直往平衡树的左子树递归就能找到答案,我们先将原始数组下的最小相邻差值记录下来,方便我们以后修改。

然后对于每一次插入操作 i k 来说,其影响的只有k前后两点之间差值,即删去第 i 个元素的最后一个插入元素与第 i+1 个元素之间的差值,向平衡树插入第 i 个元素的最后一个插入元素与 k 的差值和 k 与第 i+1 个元素的差值。

代码实现大意如下:

Delete(root,a[i].last-a[i+1].head);
Insert(root,a[i].last-k);
Insert(root,a[i+1].head-k);

最后我们找一下最左边的节点,更新一下答案即可。

操作三

类似操作二,我们通过排序预处理出最小的差值,同时将整个序列插入另一棵平衡树。在每次插入操作 i k 时,我们直接将 k 插入平衡树,并查找其前驱后继,计算差值,然后更新答案即可。

代码实现

下面给出主要的核心代码(为了精简去掉了平衡树板子的部分)

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int INF=0x3f3f3f3f;
int n,m;
int ans_1,ans_2;
struct pointers
{
	int head;
	int Next;
}a[N];
int b[N];
bool flag=0;
void deals(int i,int k)
{
	//操作2 
	if(i==n) //特判最后一位 
	{
		ins2(roots,abs(a[i].Next-k)); //最后一位只需要增加一个答案即可 
	}
	else
	{
		int kill_1=abs(a[i].Next-a[i+1].head);
		int add_1=abs(a[i].Next-k);
		int add_2=abs(a[i+1].head-k);
		del(roots,kill_1);
		ins2(roots,add_1);
		ins2(roots,add_2); //一次删除两次插入 
	}
	ans_1=Find(roots);  //取最左节点更新答案 
	//操作3 
	if(flag==0) //这里特判一下有重复数据的地方 
	{
		ins(root,k);
		int pre=getpre(root,k);
		int nxt=getnxt(root,k); //求前驱后继 
		ans_2=min(ans_2,min(abs(pre-k),abs(nxt-k))); //更新答案 
	}
	if(flag==1) ans_2=0;//(有相同数据则差值永远为0) 
	
	a[i].Next=k; //插入操作 
}
void init() //初始化 预处理答案 
{
	ans_2=INF;
	//操作2的预处理 
	for(int i=1;i<n;i++) //这里b[i]作为临时数组来用 
	{
		ins2(roots,abs(b[i]-b[i+1])); //插入所有的相邻差值 
	}
	ans_1=Find(roots); //先求一下答案
	//操作3的预处理 
	sort(b+1,b+1+n); //先排序 
	for(int i=1;i<n;i++)
	{
		ans_2=min(ans_2,abs(b[i]-b[i+1])); //求最小差值 
		ins(root,b[i]); //插入所有的点 
	}
	ins(root,b[n]); //别落下最后一个点 
}
int main() //非常简洁的主函数 (bushi)
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>b[i];
		a[i].head=b[i];
		a[i].Next=b[i];
	}
	init();	
	string op;
	for(int i=1;i<=m;i++)
	{
		cin>>op;
		if(op=="INSERT")
		{
			int j,k;
			cin>>j>>k;
			deals(j,k);
		}
		else if(op=="MIN_GAP")
		{
			cout<<ans_1<<'\n';
		}
		else
		{
			cout<<ans_2<<'\n';	
		}
	}
	return 0;
}

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值