题解 P6881 【[JOI 2020 Final] 火事】

xtx1092515503

2020-12-03 10:08:39

Solution

数据结构好题。 ------------ 我们考虑画出每一时刻所有位置的值: ![](https://cdn.luogu.com.cn/upload/image_hosting/qmgzwyrw.png) 这其中,我们可以找到最标准的情形:里面的元素 $\color{cyan}{7}$。可以看到,随着时间增加,它呈现一个平行四边形。 那么这个平行四边形由上面构成呢?我们考虑用单调栈找出每个位置向左向右第一个比它大的位置 $l_i,r_i$。则我们会发现,平行四边形的四个顶点是 $(i,0),(r_i-1,r_i-i-1),(r_i-1,r_i-l_i-2),(i,i-l_i-1)$。 我们考虑此时的询问,在坐标系上,就成为了一段水平线段。考虑差分一下,就变成了两条沿 $x$ 轴负方向的的射线。 考虑把一个平行四边形拆成三个三角形: ![](https://cdn.luogu.com.cn/upload/image_hosting/75of4m75.png) 如图,绿色平行四边形=大三角形-蓝三角形-紫三角形。 然后我们发现,每个三角形的形状都是一样的,并且都有一条腰在 $x$ 轴上。故我们可以用其上方顶点来唯一确定一个三角形。则有大三角形顶点 $(r_i-1,r_i-l_i-2)$,蓝三角形顶点 $(i-1,i-l_i-2)$,紫三角形顶点 $(r_i-1,r_i-i-2)$。 我们考虑一条沿 $x$ 轴负方向的射线与三角形的交线段长度: ![](https://cdn.luogu.com.cn/upload/image_hosting/35jshr42.png) 如图,如果该射线是绿线(原点在三角形内),可以将其平移至紫线位置,然后就可以拆括号计算了。如果该直线是棕线,则也可以简单计算。 于是我们可以用扫描线维护对于递增的射线原点的 $x$ 坐标,相应的位于其左侧的三角形们和位于其右侧的三角形们,然后分别转移即可。 至于如何转移,可以各通过两棵树状数组,一棵维护系数和,一棵维护带权坐标和,然后分别转移即可。共需四棵树状数组。 时间复杂度 $O(n\log n)$。 代码: ```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll; int n,m,a[200100],l[200100],r[200100],stk[200100],tp,lim,mil; ll res[200100]; namespace BIT1{//a BIT for triangular summary. ll t1[600100],t2[600100];//t1 for weighted sum, t2 for common sum. void ADD(int x,int y){ x--; ll tmp=1ll*y*x; for(int i=x+n+1;i<=3*n;i+=i&-i)t1[i]+=tmp,t2[i]+=y; } ll SUM(int x){ ll ret=0; for(int i=x+n+1;i;i-=i&-i)ret-=t1[i],ret+=t2[i]*x; return ret; } } namespace BIT2{ ll t1[600100],t2[600100]; void ADD(int x,int y){ x++; ll tmp=1ll*y*x; for(int i=x+n+1;i;i-=i&-i)t1[i]+=tmp,t2[i]+=y; } ll SUM(int x){ ll ret=0; for(int i=x+n+1;i<=3*n;i+=i&-i)ret+=t1[i],ret-=t2[i]*x; return ret; } } void ADD1(int x,int y,int z){BIT1::ADD(x-y,z);} void ADD2(int x,int y,int z){BIT2::ADD(y,z);} struct Query{ int x,y,id; Query(int u=0,int v=0,int w=0){x=u,y=v,id=w;} friend bool operator <(const Query &x,const Query &y){return x.x<y.x;} }q[400100]; struct Tri{ int x,y,a; Tri(int u=0,int v=0,int w=0){x=u,y=v,a=w;} friend bool operator <(const Tri &x,const Tri &y){return x.x<y.x;} }p[800100]; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=n;i>=1;i--){ while(tp&&a[i]>a[stk[tp]])l[stk[tp--]]=i; stk[++tp]=i; } while(tp)l[stk[tp]]=stk[tp]-n-1,tp--; for(int i=1;i<=n;i++){ while(tp&&a[stk[tp]]<=a[i])r[stk[tp--]]=i; stk[++tp]=i; } while(tp)r[stk[tp--]]=n+1; for(int i=1;i<=n;i++){ p[++lim]=Tri(i-1,i-l[i]-2,-a[i]); p[++lim]=Tri(r[i]-1,r[i]-l[i]-2,a[i]); p[++lim]=Tri(r[i]-1,r[i]-i-2,-a[i]); } for(int i=1;i<=lim;i++)ADD1(p[i].x,p[i].y,p[i].a); sort(p+1,p+lim+1); for(int i=1,t,l,r;i<=m;i++){ scanf("%d%d%d",&t,&l,&r); q[++mil]=Query(l-1,t,-i); q[++mil]=Query(r,t,i); } sort(q+1,q+mil+1); for(int i=1,j=1;i<=mil;i++){ while(j<=lim&&p[j].x<q[i].x)ADD1(p[j].x,p[j].y,-p[j].a),ADD2(p[j].x,p[j].y,p[j].a),j++; int lambda=(q[i].id>0?1:-1);q[i].id=abs(q[i].id); res[q[i].id]+=(BIT1::SUM(q[i].x-q[i].y)+BIT2::SUM(q[i].y))*lambda; } for(int i=1;i<=m;i++)printf("%lld\n",res[i]); // for(int i=1;i<=n;i++)printf("%d %d\n",l[i],r[i]); return 0; } ```