题解:P2286 宠物收养场

· · 题解

这道题我是拿权值树状数组搭配离散化 O(n \log n) 水过的,码量很小并且只用一棵树。这篇题解里就会讲解这种做法。

前言:

并非所有需要平衡树的题目都能用权值树状数组代替。

受限于树状数组的性质,如果需要处理本题这样值域较大的数据,需要先进行离散化,因此必须离线。如果题目强制要求在线,那么权值树状数组就只能处理值域较小的数据了。

解法:

以下记树状数组维护的桶为 g,树状数组为 t。

分析题意,发现这道题要实现这么几个操作:插入,删除,求前驱,求后继。

权值树状数组上的插入和删除都很简单,插入即 g_i \gets g_i+1,删除即g_i \gets g_i-1。

那么,问题的关键在于在 O(\log n) 的复杂度内求出前驱和后继。

记 krank(k) 为当前树状数组中第 k 小的数,sum(k) 为当前树状数组中权值不大于 k 的数的数量,那么:

$k$ 的后继可以表示为 $krank(sum(k)+1)$。 ![](https://cdn.luogu.com.cn/upload/image_hosting/cy2tjvb3.png) 现在,我们就把求前驱和后继的问题转化为了求一个权值树状数组上第 $k$ 小的问题。 对于这个问题,可以利用树状数组的性质倍增解决。 对于一个树状数组上的位置 $x$ ,如果加上一个小于 $\operatorname{lowbit}(x)$ 的 $2^i$ ,那么 $sum(x)$ 增加的值正好就是跳过的一片区域的总权值。 例如:$sum(6)-sum(4)=g_5+g_6=t_6$。 因此考虑倍增,从 $0$ 开始依次尝试让 $x$ 往后跳 $2^{\log n},2^{\log n-1},2^{\log n-2},...,2^0$,每次跳跃都将一个数加上树状数组对应位置的权值,根据当前数的大小来判断是否能执行本次跳跃。 给出倍增求 $krank$ 的方法如下,其中 $T= \log_2 n$: ```cpp int krank(int k){ int pos=0,val=0; for(int i=T,x,y;i>=0;--i){ x=pos+(1<<i); if(x>bn) continue; y=val+t[x]; if(y<k) pos=x,val=y; } return b[pos+1]; } ``` 此外,对于当前树状数组中存储的是宠物还是领养者,可以使用正负数来做处理,这样可以只使用一个树状数组就实现按照题意的模拟。 代码和注释如下: ```cpp #include<bits/stdc++.h> #define ID(y) lower_bound(b+1,b+bn+1,y)-b #define h(x) ((x==1)?-1:1) //用h(x)转换类型,宠物记作1,领养者记作-1 #define int long long using namespace std; const int N=8e4+5,inf=1e12; int t[N],a[N],b[N],bn,T; int n,cnt,ans; struct node{ int op,v; }f[N]; int lowbit(int x){ return x&(-x); } void add(int pos,int x){ while(pos<=bn){ t[pos]+=x; pos+=lowbit(pos); } } int sum(int pos){ int res=0; while(pos){ res+=t[pos]; pos-=lowbit(pos); } return res; } int krank(int k){ if(k<1) return -inf; if(k>abs(cnt)) return inf; //特判不存在前驱或后继的情况 int pos=0,val=0; for(int i=T,x,y;i>=0;--i){ x=pos+(1<<i); if(x>bn) continue; y=val+t[x]; if(y<k) pos=x,val=y; } return b[pos+1]; } signed main(){ cin>>n; for(int op,v,i=1;i<=n;++i){ cin>>op>>v; f[i].op=op; f[i].v=v; a[++cnt]=v; b[cnt]=v; } sort(b+1,b+n+1); bn=unique(b+1,b+n+1)-b-1; T=log2(bn); cnt=0; for(int i=1,x,y,A,B;i<=n;++i){ x=f[i].op; y=f[i].v; if(h(x)*cnt<0){ //根据乘法的性质,两者之积为负数即代表加入元素与树中元素不同类 A=krank(sum(ID(y)-1)); B=krank(sum(ID(y))+1); if(y-A<=B-y) add(ID(A),-1),ans+=(y-A); else add(ID(B),-1),ans+=(B-y); } else add(ID(y),1); cnt+=h(x); //利用cnt的大小和正负存储当前树状数组中元素的类型和个数 } cout<<ans%1000000; return 0; } ``` 希望这篇题解能够帮到你!