LGP6881 [JOI 2020 F] 火事 学习笔记
LGP6881 [JOI 2020 F] 火事 学习笔记
前言
火事是日文,对应的中文是火灾。但我还是觉得火事好听。
终于,这道题也终于能被攻克掉了吗。曾经屡战屡败,拖延时长仅次于喵了个喵的题目,现在终于要拿下来了……
感谢 \(\texttt{UserUnauthorized}\) 的优质配图题解。能比某些强校选手随手写的东西详尽,已然是一种人文关怀了。
图暂时鸽了,可以参考上文的配图,见谅。
题意简述
给定一个序列 \(S[0]\),长度为 \(n\)。
我们这样生成 \(S[i]\):\(\forall i\in [1,n]\),\(S[i]_j=\max(S[i-1]_j,S[i-1]_{j-1})\)。
有 \(m\) 个询问。每个形如问你 \(\sum_{k=l_j}^{r_j}S[t_j]_k\)。请对它们做!出!回!答!
\(n,m\le 2\times 10^5\),\(V\le 10^9\)。
本题不需要线段树即可简单解决,所以请不要使用线段树。
做法解析
这显然是某种扫描线和数点题。因为它整个题都只有按某种简单模式扩散出的 \(n\times(n+1)\) 个数据与若干个简单的求和询问。
我们考虑把所有 \(S[i]\) 写在一起,观察数据变化的特点。
如果你把每个元素看成一个颜色,你就会发现:每个颜色都形成了一个比较规矩的图形:可能是三角形,可能是平行四边形,也可能是一个梯形。
这些图形有什么特点呢?答案是:它们都能被差分成最多三个三角形,而且这个差分是比较容易做的,因为这图形的各个坐标都不难求:我们对于 \(S[0]\) 的每个元素,用单调栈即可求出来左边最终“吞噬”它的元素下标和右边最终“挡住”它的元素下标,后面推导一下即可。
所以现在修改就变成了若干个三角形(数量在 \([n,3n]\) 左右)修。这怎么办啊?别人说对于斜着的东西要“平移”这这那那的,但是我在学扫描线的时候只见过各种横平竖直的修改查询,并没有听说过“平移”啊。
那你现在就知道了——
我们首先新建一个平面,并把 \(t\) 轴顺时针旋转 \(45\) 度。为什么我们要新建一个平面呢?因为这样子的话,原平面上(以我们的视角来看)指着右下方 \(45\) 度倾斜的那些边,到了这个平面上就是直的了。你看,原来那些三角形的斜边被拉直了,左边的直角边被拉斜了。
好吧,形式化地说,我们将原平面坐标 \((i,j)\) 的点映射到新平面的 \((i,j+n-i)\) 处。
“这一步非常重要!台上讲的老哥轻描淡写,我想了好几天(杠难过)”——另一篇题解
这样,我们根据一个图形 \(t\) 轴方向的斜与直,选择修改它的平面,我们就总是能简单地维护它们了。而在这道题,其用途就在于:这个三角形可以被差分为两个(不同平面上的)矩形之差!所以这个问题就能做了!
好吧,实际上差完之后还会多出来一个倒三角形,然而没有询问会碰到那里,因为他在 \(t\) 轴左侧。
现在问题变为了:有两个平面,其上分别由若干个左右个矩形加,每个平面上都有 \(m\) 个 \(1\times len_j\) 的询问。
这个你应该很好懂了,因为这就是区间加区间和树状数组的事情了。当然,因为我们有两个平面,所以也需要两个树状数组。
以防你忘了区间加区间和树状数组:请移步这里。
代码实现
实则代码很短。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=2e5+5;
int N,M,A[MaxN],L[MaxN],R[MaxN],X,Y,Z;
struct BinidTree{
int n;lolo ta[MaxN*3],ts[MaxN*3];
void init(int x){n=x;fill(ta,ta+n+1,0),fill(ts,ts+n+1,0);}
int lowbit(int x){return x&(-x);}
void add(int p,lolo x){for(int q=p;q&&q<=n;q+=lowbit(q))ta[q]+=x,ts[q]+=p*x;}
void adds(int l,int r,lolo x){add(l,x),add(r+1,-x);}
lolo gts(int p){lolo res=0;for(int q=p;q;res+=(p+1)*ta[q]-ts[q],q-=lowbit(q));return res;}
lolo getsum(int l,int r){return gts(r)-gts(l-1);}
}BiT1,BiT2;
int stk[MaxN],ktp;lolo ans[MaxN];
struct anob{int l,r,id;};
vector<anob> Q[MaxN],S[MaxN][2];
void tsolve(int lt,int len,int lx,int val){
if(lx>1)S[lt][0].push_back({1,lx-1,-val});
if(lx>1)S[lt+len][0].push_back({1,lx-1,val});
S[lt][1].push_back({1,N-lt+lx,val});
S[lt+len][1].push_back({1,N-lt+lx,-val});
}
int D[3000][3000];
int main(){
readis(N,M);
BiT1.init(N*3),BiT2.init(N*3);
for(int i=1;i<=N;i++)readi(A[i]);
ktp=0;for(int i=N;i>=1;i--){
while(ktp&&A[i]>A[stk[ktp]])L[stk[ktp--]]=i;
stk[++ktp]=i;
}
ktp=0;for(int i=1;i<=N;i++){
while(ktp&&A[i]>=A[stk[ktp]])R[stk[ktp--]]=i;
stk[++ktp]=i;
}
while(ktp)R[stk[ktp--]]=N+1;
for(int i=1,tlen,flen;i<=N;i++){
tlen=L[i]?i-L[i]:N+1-(R[i]-i);
flen=L[i]?R[i]-L[i]:N+1;
tsolve(0,flen,i,A[i]);
tsolve(R[i]-i,tlen,R[i],-A[i]);
if(L[i])tsolve(i-L[i],flen-tlen,i,-A[i]);
}
for(int i=1;i<=M;i++)readis(Z,X,Y),Q[Z].push_back({X,Y,i});
for(int i=0;i<=N;i++){
for(auto [l,r,x] : S[i][0])BiT1.adds(l,r,x);
for(auto [l,r,x] : S[i][1])BiT2.adds(l,r,x);
for(auto [l,r,id] : Q[i]){
ans[id]+=BiT1.getsum(l,r);
ans[id]+=BiT2.getsum(N-i+l,N-i+r);
}
}
for(int i=1;i<=M;i++)writil(ans[i]);
return 0;
}
后记
过了这个,还拦着我的应该只有擂台游戏了?
好吧,不止。还有水杯降温,序列变换,山河重整,岁月。
但它们应该也拦不住我的。——\(\texttt{20251013}\)
\(\texttt{UPD on 20251014}\):水杯降温过了。
浙公网安备 33010602011771号