LGP6881 [JOI 2020 F] 火事 学习笔记

LGP6881 [JOI 2020 F] 火事 学习笔记

\(\texttt{Luogu Link}\)

前言

火事是日文,对应的中文是火灾。但我还是觉得火事好听。

终于,这道题也终于能被攻克掉了吗。曾经屡战屡败,拖延时长仅次于喵了个喵的题目,现在终于要拿下来了……

感谢 \(\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}\):水杯降温过了。

posted @ 2025-10-22 10:42  矞龙OrinLoong  阅读(4)  评论(0)    收藏  举报