【蓝桥杯 第八大奇迹】
题目描述
在一条 R 河流域,繁衍着一个古老的名族 Z。
他们世代沿河而居,也在河边发展出了璀璨的文明。
Z 族在 R 河沿岸修建了很多建筑,最近,他们热衷攀比起来。
他们总是在比谁的建筑建得最奇特。
幸好 Z 族人对奇特的理解都差不多,他们很快给每栋建筑都打了分,这样评选谁最奇特就轻而易举了。
于是,根据分值,大家很快评出了最奇特的建筑,称为大奇迹。
后来他们又陆续评选了第二奇特、第二奇特、……、第七奇特的建筑,依次称为第二大奇迹、第三大奇迹、……、第七大奇迹。
最近,他们开始评选第八奇特的建筑,准备命名为第八大奇迹。
在评选中,他们遇到了一些问题。
首先,Z 族一直在发展,有的建筑被拆除又建了新的建筑,新建筑的奇特值和原建筑不一样,这使得评选不那么容易了。
其次,Z 族的每个人所生活的范围可能不一样,他们见过的建筑并不是所有的建筑,他们坚持他们自己所看到的第八奇特的建筑就是第八大奇迹。
Z 族首领最近很头疼这个问题,他害怕因为意见不一致导致 Z 族发生分歧。
他找到你,他想先了解一下,民众自己认为的奇迹是怎样的。
现在告诉在 R 河周边的建筑的变化情况,以及在变化过程中一些人的生活范围,请编程求出每个人认为的第八大奇迹的奇特值是多少。
解题思路
看到这道题,还是很眼熟的,记得以前应该是做过的,但是时间太久忘了,今天又来复习一下,这道题可以用树套树或者整体二分来做,今天就先写个树状数组讨权值线段树版本的,之后复习了整体二分再来写一篇整体二分的。
首先树状数组可以维护的是前缀和的信息,比如用来求区间和。权值线段树可以用来求关于全局第K大这种问题,当把这两个算法结合在以前的时候就可以求解区间第K大了。
注意树状数组上面的每个点都是一颗权值线段树,这颗线段树维护的是树状数组这个点代表的区间的所有权值。只是以前树状数组关于求1-R的区间信息的时候,答案是保存在i(i=i&-i)这一系列的点上的。现在一个区间的信息是保存在以i(i=i&-1)为根的一系列权值线段树上面的,把这些权值线段树上的所有信息统合在一起就可以知道整个区间的信息了。
比如,X[i]中存放了权值在[l,r]这个区间的所有线段树(1,R)的信息,Y[i]中存放了权值在[l,r]这个区间的所有线段树(1,L-1)的信息
那么如果Σ{sum[rs[X(i)]]}-Σ{sum[rs[Y(i)]]}>=k,那么我们可以直接去右边区间找,否则去左边区间
注:L,R是求解的具体区间,[l,r]是线段树中的权值区间。sum[g]表示以g为根的权值线段树下有多少个值。k是取区间第k大。
上代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define N 200050
int root[N],ls[N*40],rs[N*40],sum[N*40];
int X[N],Y[N];
int cnt=0,L,n;
void tr_change(int &g,int l,int r,int x,int v){
if(!g)g=++cnt;
if(l==r){
sum[g]+=v;return;
}
int mid=l+r>>1;
if(x<=mid){
tr_change(ls[g],l,mid,x,v);
sum[g]=sum[ls[g]]+sum[rs[g]];
}else{
tr_change(rs[g],mid+1,r,x,v);
sum[g]=sum[ls[g]]+sum