【题解】[JOI 2020 Final] 火事
这题看起来人畜无害,非常小清新的 DS 题,但写起来就不会这么想了。
题目大意,给定一个长度为 \(N\) 的序列 \(S\) ,需要回答 \(Q\) 次询问,每次询问 \(\sum\limits_{L\le i\le R}\max\limits_{i-T\le j\le i}\{S_j\}\) 。
子任务 \(1\) ,直接模拟,时间复杂度 \(\mathcal{O}(QN^2)\) 。
子任务 \(2\) ,\(T_i\) 相等,直接算出 \(T\) 时刻的序列,然后求一个前缀和即可。计算序列时可以单调队列做到 \(\mathcal{O}(N)\) 。
子任务 \(3\) ,\(L_i=R_i\) ,单点询问,直接计算 \(\max\limits_{L-T\le i\le L}\{S_i\}\) ,ST 表可以做到 \(\mathcal{O}(N\log N+Q)\) 。
子任务 \(4\),\(1\le S_i\le 2\) 。
我们不妨列出一个表格,\(S_{i,j}\) 表示时刻 \(i\) ,序列第 \(j\) 位的是 \(1\) 还是 \(2\) 。
先将整个表格用 \(1\) 填充,然后发现如果第 \(i\) 位为 \(2\) ,那么表格中位于直线 \(y=x-i\) 斜上方(包括)的且位置 \(\ge i\) 的所有格子都被填上 \(2\) 。
这就是扫描线模板,差分一下可以做到 \(\mathcal{O}(N+Q\log N)\) 。
对于满分做法,我们考虑延续前面的思路。
对于每一个位置 \(i\) 上的数,如果前面有比它大的数,那么它在表格中填充的部分一定是平行四边形,否则一定是一个直角梯形。
对于一个平行四边形,我们拆成三个在 \(x\) 轴上的等腰直角三角形。
现在只用考虑三角形的情况。
我们可以用一条斜率为 \(1\) 的直线和 \(x\) 轴切出一个等腰直角三角形的一个角。
对于这个角的对边,我们再拆成一个等腰直角三角形和一个长方形。
长方形已经可以直接做了,这两个等腰直角三角形都具有可以向右无限扩展的特点,我们可以用两条直线表示,在形如 \(y=x+b\) 的直线协下方,在形如 \(y=a\) 的直线上方。
转化为差分,我们需要支持区间加和区间求和。可以树状数组再做一次差分,也可以直接线段树。
由于笔者写法比较丑需要负数下标,所以使用了线段树。时间复杂度 \(\mathcal{O}((N+Q)\log N)\) 。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 500005
#define int long long
using namespace std;
int n,m,s[N],sta[N],pre[N],nxt[N],ans[N],top;
typedef pair<int,int> Pr;
vector<Pr>c[N],d[N];
struct seg{
struct node{
int l,r,tag,sum;
}a[N<<2];
#define L a[x].l
#define R a[x].r
#define ls (x<<1)
#define rs (ls|1)
#define T a[x].tag
#define S a[x].sum
inline int g(int l,int r){return l+(r-l)/2;}
void build(int x,int l,int r){
L=l;R=r;S=T=0;
if(l==r)return ;
int mid=g(l,r);
build(ls,l,mid);
build(rs,mid+1,r);
}
void pushup(int x,int val){T+=val;S+=(R-L+1)*val;}
void down(int x){if(T)pushup(ls,T),pushup(rs,T),T=0;}
void change(int x,int l,int r,int val){
if(L>=l&&R<=r)pushup(x,val);
else{
down(x);int mid=g(L,R);
if(mid>=l)change(ls,l,r,val);
if(mid<r)change(rs,l,r,val);
S=a[ls].sum+a[rs].sum;
}
}
int ask(int x,int l,int r){
if(L>=l&&R<=r)return S;
down(x);int sum=0,mid=g(L,R);
if(mid>=l)sum+=ask(ls,l,r);
if(mid<r)sum+=ask(rs,l,r);
return sum;
}
}p,q;
void tri(int l,int r,int val){
//cout<<"cc "<<l<<" "<<r<<" "<<val<<endl;
if(l>r)return;
c[0].push_back(make_pair(l,val));
c[r-l+1].push_back(make_pair(l,-val));
d[0].push_back(make_pair(r+1,-val));
d[r-l+1].push_back(make_pair(r+1,val));
}
void par(int l,int r,int len,int val){
int st=l-len+1;
tri(st,r,val);tri(st,l-1,-val);tri(l+1,r,-val);
}
struct node{int t,l,r,op;bool operator<(const node o)const{return t<o.t;}}u[N];
signed main(){
scanf("%lld%lld",&n,&m);int ll=-n-5,rr=n+5;
rep(i,1,n)scanf("%lld",&s[i]);
rep(i,1,n){
while(top&&s[sta[top]]<=s[i])nxt[sta[top]]=i,top--;
if(top)pre[i]=sta[top];else pre[i]=-n-2;
sta[++top]=i;
}
rep(i,1,n)if(!nxt[i])nxt[i]=n+1;
//rep(i,1,n)printf("ss %lld %lld \n",pre[i],nxt[i]);
rep(i,1,n)par(i,nxt[i]-1,i-pre[i],s[i]);
rep(i,1,m)scanf("%lld%lld%lld",&u[i].t,&u[i].l,&u[i].r),u[i].op=i;
sort(u+1,u+m+1);int j=1;
p.build(1,ll,rr);q.build(1,ll,rr);
rep(t,0,n){
//cout<<"st "<<t<<endl;
for(int i=0;i<(int)c[t].size();i++){
p.change(1,c[t][i].first,rr,c[t][i].second);
//cout<<"Add A "<<c[t][i].first<<" "<<c[t][i].second<<endl;
}
for(int i=0;i<(int)d[t].size();i++){
q.change(1,d[t][i].first,rr,d[t][i].second);
//cout<<"Add B "<<d[t][i].first<<" "<<d[t][i].second<<endl;
}
while(j<=m&&u[j].t==t){
ans[u[j].op]=q.ask(1,u[j].l,u[j].r)+p.ask(1,u[j].l-t,u[j].r-t);
j++;
}
}
rep(i,1,m)printf("%lld\n",ans[i]);
return 0;
}