• 【2019.8.8 慈溪模拟赛 T2】query(query)(分治+分类讨论)


    分治

    首先,我们考虑分治处理此问题。

    每次处理区间([l,r])时,我们先处理完([l,mid])([mid+1,r])两个区间的答案,然后我们再考虑计算左区间与右区间之间的答案。

    处理的时候就需要分类讨论。

    分类讨论

    (Mn_x)(lle xle mid)时表示左区间的后缀最小值,(mid+1le xle r)时表示右区间的前缀最小值;(Mx_x)同理根据(x)的取值范围分别表示左区间的后缀最大值和右区间的前缀最大值。

    考虑在左区间枚举左端点(i),用双指针在右区间移动,把右区间划分成三部分。

    第一部分,这段区间内的(x)满足(Mn_xge Mn_i,Mx_xle Mx_i)

    那么当右端点取在这段区间内时,答案都取(Mn_i&Mx_i)

    第二部分,这段区间内的(x)满足(Mn_xge Mn_i,Mx_x>Mx_i)或者(Mn_x<Mn_i,Mx_xle Mx_i)

    此时最小值或最大值中的其中一个会取这段区间中的值,另一个会取(i)位置的值。

    这里以这段区间中取最小值,(i)位置上取最大值为例。

    那么就是将这段区间内的(Mn_x)全都(&)(Mx_i)之后再求和。

    只要将每个(Mn_x)二进制分解一下,然后每一位求前缀和。

    询问时枚举二进制下每一位,若(Mx_i)这一位上有值,就计入答案,否则忽略不计。

    第三部分,这段区间内的(x)满足(Mn_x<Mn_i,Mx_x>Mx_i)

    这时候答案取(Mn_x&Mx_x),只要预处理一下就可以了。

    代码

    #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 LV 20
    #define LL long long
    #define min(x,y) ((x)<(y)?(x):(y))
    #define max(x,y) ((x)>(y)?(x):(y))
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    using namespace std;
    int n,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 tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		char c,*A,*B,FI[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		#undef D
    }F;
    class DivideSolver//分治
    {
    	private:
    		int Mn[N+5],Mx[N+5],Mn_[N+5][LV+5],Mx_[N+5][LV+5];LL ans,S[N+5];
    		I void Work(int *sl,int *sr,CI v)
    		{
    			for(RI i=0;i<=LV;++i) v>>i&1&&(ans+=(1LL<<i)*(sr[i]-sl[i]));//枚举二进制下每一位计算答案
    		}
    		I void Divide(CI l,CI r)
    		{
    			if(l>=r) return;RI i,j,mid=l+r>>1;Divide(l,mid),Divide(mid+1,r);//递归处理子区间
    			for(Mn[mid]=Mx[mid]=a[mid],i=mid-1;i>=l;--i)//预处理左区间后缀最小值/最大值
    				Mn[i]=min(a[i],Mn[i+1]),Mx[i]=max(a[i],Mx[i+1]);
    			for(Mn[mid+1]=Mx[mid+1]=a[mid+1],i=mid+2;i<=r;++i)//预处理右区间前缀最小值/最大值
    				Mn[i]=min(a[i],Mn[i-1]),Mx[i]=max(a[i],Mx[i-1]);
    			memset(Mn_[mid],0,sizeof(Mn_[mid])),memset(Mx_[mid],0,sizeof(Mx_[mid]));//清空
    			for(i=mid+1;i<=r;++i) for(j=0;j<=LV;++j)//二进制分解右区间的Mn,Mx,并求前缀和
    				Mn_[i][j]=Mn_[i-1][j],Mx_[i][j]=Mx_[i-1][j],
    				Mn[i]>>j&1&&++Mn_[i][j],Mx[i]>>j&1&&++Mx_[i][j];
    			for(S[mid]=0,i=mid+1;i<=r;++i) S[i]=S[i-1]+(Mx[i]&Mn[i]);//统计右区间Mn[x]&Mx[x]的和
    			RI pl=mid,pr=mid+1;for(i=mid;i>=l;--i)//在左区间枚举
    			{
    				W(pl<r&&Mn[pl+1]>=Mn[i]&&Mx[pl+1]<=Mx[i]) ++pl;//指针在右区间移动
    				W(pr<=r&&(Mn[pr]>=Mn[i]||Mx[pr]<=Mx[i])) ++pr;//指针在右区间移动
    				ans+=1LL*(pl-mid)*(Mn[i]&Mx[i])+S[r]-S[pr-1];if(pl+1>pr-1) continue;//计算一、三两部分答案
    				Mx[pl+1]>Mx[i]?Work(Mx_[pl],Mx_[pr-1],Mn[i]):Work(Mn_[pl],Mn_[pr-1],Mx[i]);//计算第二部分答案
    			}
    		}
    	public:
    		I void Solve()
    		{
    			for(RI i=1;i<=n;++i) ans+=a[i];//考虑单点答案
    			Divide(1,n),printf("%lld",ans);//输出答案
    		}
    }D;
    int main()
    {
    	freopen("query.in","r",stdin),freopen("query.out","w",stdout);
    	RI i;for(F.read(n),i=1;i<=n;++i) F.read(a[i]);return D.Solve(),0;
    }
    
  • 相关阅读:
    并发编程之多线程(理论部分)
    基于TCP和UDP的socket
    ajax笔记 显示出所城市名称 ShowCity.aspx Html代码
    蒸饭的纱布
    ajax 笔记--不刷新实现简单的留言版 guestBook
    从表的第几条取到第几条记录
    在asp.net添加数据到XML里去
    ajax 笔记-- 写了一个不用刷新就能实现--用户名验证的例子
    今天生日
    ajax 笔记不用刷新实现数据的分页显示
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Contest20190808T2.html
Copyright © 2020-2023  润新知