LGP11094 [ROI 2021] 砍树 学习笔记
LGP11094 [ROI 2021] 砍树 学习笔记
前言
对于这一类区间操作统计合法点数的问题,不妨考虑对点计数而非模拟过程。
题意简述
数轴上有 \(n\) 个点。第 \(i\) 个点坐标为 \(a_i\),上有一棵高度为 \(h_i\) 的树。
你在砍一棵树的时候可以选择让它往左倒还是往右倒。倒的时候不能碰到别的树或者左右边界外。也就是说你需要确保:当 \((a_i,h_i)\) 被往左砍时,不应存在 \(a_i-h_i\le a_j<a_i\) 的未被砍的 \(j\),也不应有 \(a_i-h_i<a_1\)。往右同理。
\(m\) 次询问,给出 \(l,r\),表明只允许砍倒第 \([l,r]\) 棵树。对于每个询问,回答最多可以砍倒多少棵树。询问间互相独立。
\(n,q\le 5\times 10^5\),\(1\le a_i,h_i\le 10^9\),保证按 \(a_i\) 单增顺序给出所有点的信息。
做法解析
模拟肯定是不行的。你考虑对每个点考虑。考虑什么呢?首先想想,它能被砍倒与否这个判定是否是关于 \([l,r]\) 范围二分的。
实际上也确实如此。因为对同一棵树而言,\(l\) 越小就越有可能往左砍,\(r\) 越大就越有可能往右砍。
现在你的思考方向就可以往整体二分上靠了。我们考虑求出来:要保证树 \(i\) 能被砍倒,询问的 \(l\) 最靠右能是多少(记为 \(L_i\)),\(r\) 最靠左能是多少(记为 \(R_i\))。
考虑整体二分中的一个步骤,现在我们有若干需要求解的树,我们要判断它们的 \(L_i\) 是否大于等于 \(p\)。怎么判断呢?首先,我们忽略其它所有当前不考虑的树,让这些当前需要求解的树一个一个尝试往左倒下。下文较为赘述。
设我们当前考虑 \(j\) 能否倒下。你发现对于 \(k<j\),如果有一棵树 \(x\le k\) 在我们考虑 \(k\) 的时候可以倒下,那它考虑 \(j\) 的时候肯定也能倒下。所以当一棵树能倒的时候我们就让它倒掉,这样在考虑 \(j\) 的时候,留下来的树就都是之前倒不掉的了。对于这些树,它们往左倒是不可能的了,我们尝试让它们往右倒,显然此时能往右倒的树应该是形如 \((x,j)\) 的一个区间。最后我们来看看此时 \(j\) 能不能往左倒即可。
这个过程实际上就是个单调栈。所以时间复杂度显然也是对的,整体二分这一步是 \(O(n\log n)\) 的。
这时有人要问了,你为什么可以“忽略其它所有当前不考虑的树”呢?你考虑整体二分一次把一堆 \(i\) 分成两部分,一部分 \(L_i<p\) 而另一部分 \(L_i\ge p\) 的场景。对于那些 \(L_i\ge p\) 的,它们倒下的时候你那些 \(L_i<p\) 都杵着呢,说明 \(L_i<p\) 影响不到它们;对于那些 \(L_i\ge p\) 的,同理,它们杵着的时候不妨碍 \(L_i\ge p\) 那些倒下,所以它们要倒下的时候也不会被另一边挡。
处理出 \(L_i\) 和 \(R_i\) 之后怎么做呢?对于每个询问 \(l,r\),能砍下来的树 \(x\) 就是满足 \(l\le L_x\) 或 \(r\ge R_x\) 的所有 \(x\)。现在我们要求的就是:有多少 \(i\) 满足 \(l\le L_i\le i\le r\) 或者 \(l\le i\le R_i\le r\)。
这就是个简单的树状数组问题了,不幸的是我又饭堂了,所以这里又要赘述一下。
上述的问题是个“甲或乙”形式的。我们把它容斥一下变成求“满足甲的加上满足乙的减满足甲和乙的”。对于每个 \([l,r]\) 怎么求满足 \(l\le L_i\le i\le r\) 的 \(i\) 的数量?你发现因为要有 \(i\le r\),我们考虑 \(i\) 从小到大地加入每个区间,然后在询问的右端点处处理询问,问树状数组中大于等于 \(l\) 的元素数量。这样就可以了。其它两个同理。
好讲到这这题就做完了。
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=5e5+5;
int N,M,A[MaxN],H[MaxN],X,Y;
struct BinidTree{
int n,t[MaxN];
void init(int x){n=x,fill(t,t+n+1,0);}
int lowbit(int x){return x&(-x);}
void add(int p,int x){for(;p&&p<=n;p+=lowbit(p))t[p]+=x;}
int gts(int p){int res=0;for(;p;res+=t[p],p-=lowbit(p));return res;}
int getsum(int l,int r){return gts(r)-gts(l-1);}
}BiT;
int F[MaxN],stk[MaxN],ktp;
void solve(int al,int ar,const vecint& cids){
if(al==ar){
for(int x : cids)F[x]=al;
return;
}
assert(al<ar);
int amid=(al+ar+1)>>1;
ktp=0,stk[0]=amid-1;vecint lids,rids;
for(int x : cids){
if(x<amid){lids.push_back(x);continue;}
for(;ktp&&A[stk[ktp]]+H[stk[ktp]]<=A[x];ktp--);
if(A[stk[ktp]]>A[x]-H[x])lids.push_back(x),stk[++ktp]=x;
else rids.push_back(x);
}
solve(al,amid-1,lids),solve(amid,ar,rids);
}
int L[MaxN],R[MaxN],ans[MaxN];
struct quer{int x,id;};
vector<quer> Ql[MaxN],Qr[MaxN];
vecint D[MaxN];
int main(){
readis(N,M);
for(int i=1;i<=N;i++)readis(A[i],H[i]);
vecint ids;for(int i=1;i<=N;i++)ids.push_back(i);
A[0]=A[1],solve(0,N,ids);
for(int i=1;i<=N;i++)L[i]=F[i];
reverse(A+1,A+N+1),reverse(H+1,H+N+1);
for(int i=1;i<=N;i++)A[i]*=-1;
A[0]=A[1],solve(0,N,ids);
for(int i=1;i<=N;i++)R[N+1-i]=N+1-F[i];
for(int i=1;i<=M;i++)readis(X,Y),Ql[X].push_back({Y,i}),Qr[Y].push_back({X,i});
for(int i=1;i<=N;i++)D[R[i]].push_back(L[i]);
BiT.init(N);
for(int i=1;i<=N;i++){
BiT.add(L[i],1);
for(auto [x,id] : Qr[i])ans[id]+=BiT.getsum(x,N);
}
BiT.init(N);
for(int i=N;i>=1;i--){
BiT.add(N+1-R[i],1);
for(auto [x,id] : Ql[i])ans[id]+=BiT.getsum(N+1-x,N);
}
BiT.init(N);
for(int i=1;i<=N;i++){
for(int x : D[i])BiT.add(x,1);
for(auto [x,id] : Qr[i])ans[id]-=BiT.getsum(x,N);
}
for(int i=1;i<=M;i++)writil(ans[i]);
return 0;
}
后记
细节往左,细节往右,细节倒下,后面忘了。
这道题能拖这这这这这么久,怎么回事呢。哎哎。
浙公网安备 33010602011771号