题解 P2496 【[SDOI2012]体育课】

· · 题解

1. 题目大意

给定一个长度为 n 的数列 a_1,a_2,a_3,...,a_n , 并给出 m 个操作,操作类型如下:

操作1:查询区间最大值,输出最大值与 a_1 的差;

操作2:交换两个数的位置;

操作3:选择一段区间 [l,r] 并给定 t ,将区间中第 x 个数加上 x\cdot t .

### 2. 解题报告 本题的正解是分块。 首先我们先考虑操作3,对于两边的元素,我们直接暴力修改然后重构即可。那么我们如何维护整块呢? 维护 $add[ x ]$ 表示第 $x$ 块累加的 $t$ , 那我们要得到单个元素,再维护一个偏移量 $del[x]$ ,这样块中元素的权值即可表示为 $w[i]=a[i]+add[x]\times i-del[x]$. (举个例子,若给块 $[4,6]$ 加上 $2T, 3T, 4T$ ,那么$add[x]=T$ ,$del[x]=2T$,这样 $w[5]=a[5]+5T-2T=a[5]+3T$ .) 对于操作2,我们直接暴力交换然后重构块即可。 对于操作1,我们考虑在整块被修改后,如何维护块内的最大值。由于每个元素的编号 $i$ 和权值 $a_i$ 都是定值且 $i$ 单增,我们可以将每个元素看成 $(i,a_i)$ ,然后用单调栈维护一个上凸壳。这样随着 $add$ 的增大,最大元素位置一定向右移动,且元素权值呈单峰。 每个操作维护(询问)的复杂度都为 $O( n\sqrt{n} )$,再加上本题时间限制宽松,可以轻松通过。 ### 3. 参考程序 ```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll; namespace io { const int SIZE=(1<<21)+1; char ibuf[SIZE],*iS,*iT; char gc() { if(iS==iT) iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin); if(iS==iT) return EOF; return *iS++; } inline int gi() { char c; int x=0,f=1; for(;c<'0'||c>'9';c=gc())if(c=='-')f=-1; for(;c>='0'&&c<='9';c=gc())x=(x<<1)+(x<<3)+c-'0'; return x*f; } } using io::gi; const int N=100005,qN=320; int n,m,bel[N],b,s[qN][qN],tp[qN],pos[qN]; ll h[N],a[N],add[qN],del[qN]; #define top s[x][tp[x]] #define dtp s[x][tp[x]-1] #define Max(x) s[x][pos[x]] void remove(int x) { for(int i=(x-1)*b+1;i<=x*b;i++) a[i]+=add[x]*i-del[x]; add[x]=del[x]=pos[x]=tp[x]=0; } void build(int x) { memset(s[x],0,sizeof(s[x])); for(int i=(x-1)*b+1;i<=x*b;i++) { while(tp[x]>1&&(a[i]-a[top])*(top-dtp)>=(a[top]-a[dtp])*(i-top))--tp[x]; s[x][++tp[x]]=i; } for(pos[x]=1;pos[x]<=tp[x]&&a[s[x][pos[x]+1]]>=a[s[x][pos[x]]];pos[x]++); } void update(int x) { for(;pos[x]<=tp[x];pos[x]++) if(a[s[x][pos[x]+1]]+add[x]*s[x][pos[x]+1]<a[s[x][pos[x]]]+add[x]*s[x][pos[x]]) break; } int main() { n=gi(),m=gi(); b=sqrt(n); for(int i=1;i<=n;i++) bel[i]=(i-1)/b+1,a[i]=gi(); for(int i=1;i<=bel[n];i++) build(i); while(m--) { int op=gi(),l=gi(),r=gi(); if(op==1) { ll k=a[1]+add[1]-del[1]; ll mx=k; for(;bel[l]==bel[l-1]&&l<=r;l++) mx=max(mx,a[l]+add[bel[l]]*l-del[bel[l]]); for(;l+b<=r;l+=b) mx=max(mx,a[Max(bel[l])]+add[bel[l]]*Max(bel[l])-del[bel[l]]); for(;l<=r;l++) mx=max(mx,a[l]+add[bel[l]]*l-del[bel[l]]); printf("%lld\n",mx-k); } if(op==2) { remove(bel[l]),remove(bel[r]); swap(a[l],a[r]); build(bel[l]); build(bel[r]); } if(op==3) { int t=gi(),tl=l; for(;bel[l]==bel[l-1]&&l<=r;l++) a[l]+=(l-tl+1)*t; remove(bel[l-1]); build(bel[l-1]); for(;l+b<=r;l+=b) add[bel[l]]+=t,del[bel[l]]+=(tl-1)*t,update(bel[l]); for(;l<=r;l++) a[l]+=(l-tl+1)*t; remove(bel[r]); build(bel[r]); } } } ``` ### 4. 附:维护上凸壳的正确性数学证明 附赠给不能理解维护上凸壳正确性的同学: 假设现在有3个元素 $x,y,z$ ,设它们的编号分别为 $h_x, h_y, h_z$,元素大小为 $a_x,a_y,a_z$ ,权值为$w_x,w_y,w_z$ , $h_x<h_y<h_z$ 。设 $add$ 值为 $T$, 若存在 $T$ 使得 $w_y > w_x$ 且 $w_y>w_z$,则作差列出不等式: $a_x-a_y<(h_y-h_x)T$ , $a_y-a_z>(h_z-h_y)T$ . 两式整理合并可得 $\displaystyle \frac{a_z-a_y}{h_z-h_y}<\frac{a_y-a_x}{h_y-h_x}$ . 即:直线 $y\to z$ 的斜率小于直线 $x\to y$ 的斜率,故维护上凸壳。同时易发现,随着 $T$ 的不断增大,最大元素的位置右移,且最大元素左边的权值递增,右边的权值递减(即单峰)。