1 条题解

  • 0
    @ 2025-8-24 21:38:21

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar x_faraway_x
    毫无特色的 OI 入门选手

    搬运于2025-08-24 21:38:21,当前版本为作者最后更新于2018-12-02 13:39:26,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    1. 题目大意

    给定一个长度为 nn 的数列 a1,a2,a3,...,ana_1,a_2,a_3,...,a_n , 并给出 mm 个操作,操作类型如下:

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

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

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

    n,m105n,m \le 10^5 .

    2. 解题报告

    本题的正解是分块。

    首先我们先考虑操作3,对于两边的元素,我们直接暴力修改然后重构即可。那么我们如何维护整块呢?

    维护 add[x]add[ x ] 表示第 xx 块累加的 tt , 那我们要得到单个元素,再维护一个偏移量 del[x]del[x] ,这样块中元素的权值即可表示为 w[i]=a[i]+add[x]×idel[x]w[i]=a[i]+add[x]\times i-del[x].

    (举个例子,若给块 [4,6][4,6] 加上 2T,3T,4T2T, 3T, 4T ,那么add[x]=Tadd[x]=Tdel[x]=2Tdel[x]=2T,这样 w[5]=a[5]+5T2T=a[5]+3Tw[5]=a[5]+5T-2T=a[5]+3T .)

    对于操作2,我们直接暴力交换然后重构块即可。

    对于操作1,我们考虑在整块被修改后,如何维护块内的最大值。由于每个元素的编号 ii 和权值 aia_i 都是定值且 ii 单增,我们可以将每个元素看成 (i,ai)(i,a_i) ,然后用单调栈维护一个上凸壳。这样随着 addadd 的增大,最大元素位置一定向右移动,且元素权值呈单峰。

    每个操作维护(询问)的复杂度都为 O(nn)O( n\sqrt{n} ),再加上本题时间限制宽松,可以轻松通过。

    3. 参考程序

    #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,zx,y,z ,设它们的编号分别为 hx,hy,hzh_x, h_y, h_z,元素大小为 ax,ay,aza_x,a_y,a_z ,权值为wx,wy,wzw_x,w_y,w_zhx<hy<hzh_x<h_y<h_z 。设 addadd 值为 TT, 若存在 TT 使得 wy>wxw_y > w_xwy>wzw_y>w_z,则作差列出不等式:

    axay<(hyhx)Ta_x-a_y<(h_y-h_x)Tayaz>(hzhy)Ta_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}$ .

    即:直线 yzy\to z 的斜率小于直线 xyx\to y 的斜率,故维护上凸壳。同时易发现,随着 TT 的不断增大,最大元素的位置右移,且最大元素左边的权值递增,右边的权值递减(即单峰)。

    • 1

    信息

    ID
    1534
    时间
    6000ms
    内存
    125MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者