• 【2019.10.7 CCF-CSP-2019模拟赛 T2】绝对值(abs)(线段树细节题)


    找规律

    (p_i=a_{i+1}-a_i),则答案就是(sum_{i=1}^{n-1}p_i)

    考虑若将(a_i)加上(x)(边界情况特殊考虑),就相当于是将(p_{i-1})(x)(p_i)(x)

    先考虑(p_{i-1})(x)所造成的影响:

    • (p_{i-1}ge0)时,就相当于将答案加上(x)
    • (-xle p_{i-1}<0)时,原先的答案是(-p_{i-1}),新的答案是(x+p_{i-1}),所以答案加上(x+2p_{i-1})
    • (p_{i-1}<-x)时,就相当于将答案减去(x)
    • 不难发现,由于(-x=x-2x),所以第二、三两种情况可以概括为:当(p_{i-1}<0)时,答案加上(x+2max{p_{i-1},-x})

    同理,(p_i)(x)所造成的影响就是:

    • (p_ile0)时,答案加上(x)
    • (p_i>0)时,答案加上(x+max{-2p_i,-x})

    显然,若存在(p_{i-1}ge0)(p_ile0),肯定是最优的。

    否则,(p_i)就是一段小于等于(0)的数和一段大于等于(0)的数。

    在左边这一段中选择的贡献,就是(x+2p_{i-1}+x=2x+2p_{i-1}),只要求出(p_{i-1})的最大值即可。

    同理,在右边这一段中选择的贡献就是(2x-2p_i),只要求出(-p_i)的最大值即可。

    而在中间选择的贡献可以直接暴力算,就是新的绝对值之和减去旧的绝对值之和。

    以上即为大致思路,下面是具体实现中的一些细节。

    具体细节

    (Q):如何处理区间修改?

    (A):区间修改时可发现中间部分的(p)不变,只有两端发生变化,很好处理。

    (Q):如何判断是否存在(p_{i-1}ge0)(p_ile0)

    (A):可以开一棵线段树,每一位存储这一位上是否满足(p_{i-1}ge0)(p_ile0),然后区间查询即可。

    (Q):如何求(p_{i-1})(-p_i)的最大值?

    (A):另开两棵线段树,分别存储(p_i)(-p_i)

    (Q):如何求出(p_i)中小于等于(0)的部分与大于等于(0)的部分的分界点?

    (A):线段树上二分即可。

    当然,还有边界情况等一些特殊情况,需要大力讨论,注意细节!

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define max(x,y) ((x)>(y)?(x):(y))
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    #define abs(x) ((x)<0?-(x):(x)) 
    #define LL long long 
    #define INF 1e18
    using namespace std;
    int n;LL a[N+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define pc(c) (C==E&&(clear(),0),*C++=c)
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
    	public:
    		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
    		Tp I void writeln(Con Ty& x) {write(x),pc('
    ');}
    		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
    }F;
    class SegmentTreeSolver
    {
    	private:
    		LL p[N+5];
    		class SegmentTree//线段树三合一,毕竟都是单点修改+区间求最大值
    		{
    			private:
    				#define PT CI l=1,CI r=n,CI rt=1
    				#define LT x,y,l,mid,rt<<1
    				#define RT x,y,mid+1,r,rt<<1|1
    				#define PU(x) (V[x]=max(V[x<<1],V[x<<1|1]))
    				LL V[N<<2];
    			public:
    				I void Upt(CI x,Con LL& y,PT)//单点修改
    				{
    					if(l==r) return (void)(V[rt]=y);RI mid=l+r>>1;
    					x<=mid?Upt(LT):Upt(RT),PU(rt);
    				}
    				I LL Qry(CI x,CI y,PT)//区间求最大值
    				{
    					if(x<=l&&r<=y) return V[rt];RI mid=l+r>>1;LL g,t=-INF;
    					return x<=mid&&(g=Qry(LT),Gmax(t,g)),y>mid&&(g=Qry(RT),Gmax(t,g)),t;
    				}
    				I int Find(CI x,CI y,PT)//线段树上二分,求出两部分的分界点
    				{
    					if(l==r) return V[rt]>=0?l:0;RI mid=l+r>>1,t;
    					if(x<=l&&r<=y) return V[rt<<1|1]>=0?Find(RT):Find(LT);
    					if(y>mid&&(t=Find(RT))) return t;return Find(LT);
    				}
    		}S1,S2,T;
    	public:
    		I void Solve()
    		{
    			RI Qt,op,l,r,x,g;LL f,k,t=0;
    			for(RI i=1;i^n;++i) p[i]=a[i+1]-a[i],t+=abs(p[i]),S1.Upt(i,p[i]),S2.Upt(i,-p[i]);//初始化
    			for(RI i=1;i<=n-2;++i) p[i]>=0&&p[i+1]<=0&&(T.Upt(i,1),0);//初始化
    			F.read(Qt);W(Qt--) switch(F.read(op,l,r,x),op)//读入操作
    			{
    				case 1:
    					k=-2*x,l==1&&(Gmax(k,abs(p[1]-x)-abs(p[1])),++l),//特殊处理边界,避免讨论
    					r==n&&(Gmax(k,abs(p[n-1]+x)-abs(p[n-1])),--r);//特殊处理边界,避免讨论
    					if(l>r) goto Print;if(T.Qry(l-1,r-1)) {Gmax(k,2*x);goto Print;}//如果存在p[i-1]>=0且p[i]<=0的情况
    					if(p[l-1]<=0&&p[r]<=0) {f=S1.Qry(l-1,r-1),Gmax(f,-x),Gmax(k,2*f+2*x);goto Print;}//如果全部小于等于0
    					if(p[l-1]>=0&&p[r]>=0) {f=S2.Qry(l,r),Gmax(f,-x),Gmax(k,2*f+2*x);goto Print;}//如果全部大于等于0
    					g=S2.Find(l-1,r),Gmax(k,abs(p[g]+x)+abs(p[g+1]-x)-abs(p[g])-abs(p[g+1])),//处理在中间选择的贡献
    					g>l-1&&(f=S1.Qry(l-1,g-1),Gmax(f,-x),Gmax(k,2*f+2*x)),//处理在左边这一段中选择的贡献
    					g+1<r&&(f=S2.Qry(g+2,r),Gmax(f,-x),Gmax(k,2*f+2*x));//处理在右边这一段中选择的贡献
    					Print:F.writeln(t+k);break;//输出答案
    				case 2:
    					l^1&&(t-=abs(p[l-1]),p[l-1]+=x,t+=abs(p[l-1]),S1.Upt(l-1,p[l-1]),S2.Upt(l-1,-p[l-1]),0),//处理左边
    					r^n&&(t-=abs(p[r]),p[r]-=x,t+=abs(p[r]),S1.Upt(r,p[r]),S2.Upt(r,-p[r]),0),//处理右边
    					l^1&&//再处理左边
    					(
    						l^n&&(T.Upt(l-1,0),p[l-1]>=0&&p[l]<=0&&(T.Upt(l-1,1),0)),
    						l^2&&(T.Upt(l-2,0),p[l-2]>=0&&p[l-1]<=0&&(T.Upt(l-2,1),0))
    					),
    					r^n&&//再处理右边
    					(
    						r^(n-1)&&(T.Upt(r,0),p[r]>=0&&p[r+1]<=0&&(T.Upt(r,1),0)),
    						r^1&&(T.Upt(r-1,0),p[r-1]>=0&&p[r]<=0&&(T.Upt(r-1,1),0))
    					);break;//注意这里之所以要分开处理,是因为另一边对p的修改可能影响到这一边的判断
    			}
    		}
    }S;
    int main()
    {
    	freopen("abs.in","r",stdin),freopen("abs.out","w",stdout);
    	RI i;for(F.read(n),i=1;i<=n;++i) F.read(a[i]);return S.Solve(),F.clear(),0;
    }
    
  • 相关阅读:
    如何轻松的从GAC文件夹拷贝dll出来
    UIManager的用法(用于改变界面风格)
    Java休眠睡眠方法
    Thread.currentThread().getContextClassLoader()与Test.class.getClassLoader()的区别
    Openfire编译命令
    Openfire插件开发
    通过Java代码打开浏览器,本地文件目录以及ftp站点
    使用Pack200压缩你的代码
    Java 创建文件与创建文件夹
    Openfire插件制作
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Contest20191007T2.html
Copyright © 2020-2023  润新知