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