序列

我们先想最暴力的解法,就是一个一个修改并且保存每一个历史时刻各个位置的值,然后询问的时候查询历史时刻的这个位置

如果我们把每一个历史时刻的序列都并列写上,就会像这个样子

其中这个坐标系的每一个点代表序列中某个位置在某个时间的真实的值

然后我们就会发现修改操作变成了一个矩阵,查询操作是一条线段(实际上也是一个矩阵)

其中绿色的是查询,橙色的修改,某个位置在某个时间点的真实值就是原来的值加上所有橙色矩阵对其的影响

那么这种多个矩阵修改叠加影响的一定要想到扫描线!

对于一个修改操作,我们在二维平面上\(x=l\)处添加一个三元组\((0,x,i)\),其中\(0\)表示修改操作,\(x\)\(l\)表示扫描线上\([l,q]\)这个区间的值加上\(x\),同时在\(x=r+1\)处添加一个三元组\((0,-x,i)\),意义类似(注意一定是在\(x=r+1\)处而不是\(x=r\)处!细节注意!)

对于一个查询操作,我们在二维平面上\(x=p\)处添加一个三元组\((1,y,i)\),其中\(1\)表示查询操作,\(y\)\(i\)表示查询扫描线上\([0,i-1]\)这个区间上(加上序列最开始的值)不小于\(y\)的数的个数

这个时刻我们就要想到动态查询第\(k\)小这个模型,用分块处理即可

但是注意一个细节,暴力统计的时候一定要整个块都遍历,因为此时已经排了序了,块内的点可能不满足二维偏序了

还有动态查询小于\(k\)题目,看看这篇题解的做法,但是他描述的好像不太清楚,可以看下他的代码

来解释一下这篇题解

题解的思路就是,我们对于要查询的区间\([l,r]\),在\(l\)所在块和\(r\)所在的块进行暴力查询,然后这之间的块,我们需要知道每一个块中有多少个数字我们查询的数字\(k\)要小

一个很自然的想法是记cnt[i][j]表示前\(i\)个块值为\(j\)的个数,然后我们通过枚举一个小于\(k\)的数\(p\),将所有cnt[belong[r]-1][p]-cnt[belong[l]][p]加起来就好了

但是这样无疑是会超时的,于是我们使用两个分块,一个分块仍然维护原来的序列,另一个分块维护值域序列

具体来说,我们设cnt1[i][j]表示原来的序列的前\(i\)块有多少个位置的值属于值域序列的第\(j\)块,cnt2[i][j]表示原来序列的前\(i\)块有多少位置的值是\(j\)

查询的时候,我们考虑当前查询的数\(k\),在值域序列中找到其所在的块,那么对于值为\([1,k-1]\)的数来说,我们首先统计值域序列中在\(k\)所在的块前面的块里面的数值,这可以直接用cnt1数组进行统计,然后对于值域序列中在\(k\)所在的块里面的数值,我们用cnt2暴力统计,易知两种统计方法的时间复杂度都是\(O(logn)\),所以不会超时

然后是更新这个代码,题解里面的更新思路是将前缀和先变成原序列(也就是做差分),然后再进行单点更新,然后再做前缀和;其实没必要,按照一般思路更新就好了,具体见hydroOJ提交的代码的更新函数

当然这道题目完全可以用动态查询第\(k\)小的思路,时间复杂度多了一个log(如果要卡常的话就不要用long long,而是用int,这样运算会更快)

update 2024.7.30

如果这道题目要用动态查询第\(k\)小的思路,那么就需要排序,然后要用到归并;如果用动态查询小于\(k\)的思路,就不用排序了

posted @ 2023-12-31 23:30  最爱丁珰  阅读(6)  评论(0编辑  收藏  举报