【线段树维护历史值/莫队】BZOJ4540 [Hnoi2016] 序列

【题目】
BZOJ
给定一个长度为 n n n的序列 a a a Q Q Q次询问一段区间中所有连续子序列的最小值之和。
n , Q ≤ 1 0 5 , a i ≤ 1 0 9 n,Q\leq 10^5,a_i\leq 10^9 n,Q105,ai109

【解题思路】
这个标记是真的神奇啊。

不妨令 f i , j f_{i,j} fi,j表示左端点为 i i i,右端点为 j j j的区间最小值。如果我们枚举右端点来更新这个数组,不难发现就是一个区间覆盖。至于覆盖的范围可以先通过单调栈求出来。

对于一个询问 ( l , r ) (l,r) (l,r),其答案就是右端点枚举到 r r r时,历史所有区间 s u m [ l , r ] sum[l,r] sum[l,r]的和。

这个是可以用标记来维护的,具体我们对每个节点维护信息

  • l l l:区间长度(事实上并不需要特殊维护)
  • v v v:当前区间所有数之和
  • s s s:历史所有 v v v之和
    我们记录标记 ( a , b , c , d ) (a,b,c,d) (a,b,c,d),表示标记生效后:
  • v ′ = a ⋅ v + b ⋅ l v'=a\cdot v+b\cdot l v=av+bl
  • s ′ = c ⋅ v + d ⋅ l + s s'=c\cdot v+d\cdot l+s s=cv+dl+s
    大概就是下面这个意思。
    ( 1 a b 0 c d 0 0 1 ) × ( s v l ) \begin{pmatrix} 1 & a & b\\ 0 & c & d\\ 0& 0 & 1 \end{pmatrix} \times \begin{pmatrix} s \\ v \\ l \end{pmatrix} 100ac0bd1×svl
    标记的合并就是两个标记矩阵相乘。
    我们加入一个数字就是在它作为最小值的部分打一个 ( 0 , 0 , 0 , a i ) (0,0,0,a_i) (0,0,0,ai),再在 [ 1 , i ] [1,i] [1,i]打一个 ( 1 , 0 , 1 , 0 ) (1,0,1,0) (1,0,1,0)即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),然后带一个大常数吧,并不用实现两个矩阵相乘,只需要将对应系数搞出来就行了。

莫队做法显然好理解很多。

设一个点作为最小值往左右分别能扩展到 L i , R i L_i,R_i Li,Ri。(其中要一边是小于,一边是小于等于)
考虑如果我们当前已经知道了 [ l , r ] [l,r] [l,r]的答案,将它减去所有左端点在 l l l,右端点在 [ l , r ] [l,r] [l,r]的区间内的最小值即为 [ l + 1 , r ] [l+1,r] [l+1,r]的答案。

显然右端点在 [ i , R i ] [i,R_i] [i,Ri]的贡献为 a i a_i ai,在 [ R i + 1 , R R i + 1 ] [R_i+1,R_{R_i+1}] [Ri+1,RRi+1]的贡献为 a R i + 1 a_{R_i+1} aRi+1

依次类推,知道某个 p p p R p + 1 > r R_p+1>r Rp+1>r,剩下 [ p , r ] [p,r] [p,r]的贡献就是 a p a_p ap

不妨定义 a n + 1 = − ∞ a_{n+1}=-\infty an+1=,则每个点的下一个都是固定的,如果我们将 i i i R i + 1 R_i+1 Ri+1连边,边权为 a i × ( R i − i + 1 ) a_i\times (R_i-i+1) ai×(Rii+1),那么会形成一棵树形结构,转移时先求出 [ l , r ] [l,r] [l,r]中最小值的位置,将答案减去 d i s l − d i s p + a p × ( r − p + 1 ) dis_l-dis_p+a_p\times (r-p+1) disldisp+ap×(rp+1)即可。

其他转移类似。
复杂度 O ( n n ) O(n\sqrt n) O(nn )

事实证明根号算法比log算法快。

【参考代码】
线段树

#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+10;

namespace IO
{
	int read()
	{
		int ret=0,f=1;char c=getchar();
		while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return f?ret:-ret;
	}
	void write(ll x){if(x<0)x=-x,putchar('-');if(x>9)write(x/10);putchar(x%10^48);}
	void writeln(ll x){write(x);putchar('\n');}
}
using namespace IO;

namespace Segment
{
	struct tag
	{
		ll a,b,c,d;
		tag(ll _a=0,ll _b=0,ll _c=0,ll _d=0):a(_a),b(_b),c(_c),d(_d){}
		friend tag operator * (const tag&x,const tag&y)
			{return tag(y.a+x.a*y.c,y.b+x.a*y.d+x.b,x.c*y.c,x.c*y.d+x.d);}
	};
	struct Seg
	{
		#define ls (x<<1)
		#define rs (x<<1|1)
		tag t[N<<2];ll val[N<<2],sum[N<<2];
		void uptag(int x,int l,int r,const tag&v)
		{
			sum[x]=sum[x]+v.a*val[x]+v.b*(r-l+1);
			val[x]=v.c*val[x]+v.d*(r-l+1);
			t[x]=v*t[x];//wrong because t[x]=t[x]*v,the maxtrix mult
		}
		void pushdown(int x,int l,int r)
		{
			int mid=(l+r)>>1;
			uptag(ls,l,mid,t[x]);uptag(rs,mid+1,r,t[x]);
			t[x]=tag(0,0,1,0);
		}
		void pushup(int x){val[x]=val[ls]+val[rs];sum[x]=sum[ls]+sum[rs];}
		void update(int x,int l,int r,int L,int R,const tag&v)
		{
			if(L<=l && r<=R){uptag(x,l,r,v);return;}
			pushdown(x,l,r);
			int mid=(l+r)>>1;
			if(L<=mid) update(ls,l,mid,L,R,v);
			if(R>mid) update(rs,mid+1,r,L,R,v);
			pushup(x);
		}
		ll query(int x,int l,int r,int L,int R)
		{
			if(L<=l && r<=R) return sum[x];
			pushdown(x,l,r);
			int mid=(l+r)>>1;ll res=0;
			if(L<=mid) res+=query(ls,l,mid,L,R);
			if(R>mid) res+=query(rs,mid+1,r,L,R);
			return res;
		}
		#undef ls
		#undef rs
	}tr;
}
using namespace Segment;

namespace DreamLolita
{
	int n,Q,top,a[N];
	ll ans[N];
	pii stk[N];
	vector<pii>q[N];
	void solution()
	{
		n=read();Q=read();
		for(int i=1;i<=n;++i) a[i]=read();
		for(int i=1,x,y;i<=Q;++i)
			x=read(),y=read(),q[y].pb(mkp(x,i)); 
		for(int i=1;i<=n;++i)
		{
			while(top && stk[top].fi>=a[i]) --top;
			tr.update(1,1,n,stk[top].se+1,i,tag(0,0,0,a[i]));
			tr.update(1,1,n,1,i,tag(1,0,1,0));
			stk[++top]=mkp(a[i],i);
			for(int j=0;j<(int)q[i].size();++j)
				ans[q[i][j].se]=tr.query(1,1,n,q[i][j].fi,i);
		}
		for(int i=1;i<=Q;++i) writeln(ans[i]);
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("BZOJ4540.in","r",stdin);
	freopen("BZOJ4540.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}

莫队

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e5+10,inf=1e9,lim=308;

namespace IO
{
	int read()
	{
		int ret=0,f=1;char c=getchar();
		while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return f?ret:-ret;
	}
	void write(ll x){if(x<0)x=-x,putchar('-');if(x>9)write(x/10);putchar(x%10^48);}
	void writeln(ll x){write(x);putchar('\n');}
}
using namespace IO;

namespace DreamLolita
{
	int n,Q,top;
	int bl[N],stk[N],L[N],R[N],a[N];
	int Log[N],fc[25],st[19][N];
	ll res,ans[N],sl[N],sr[N];
	struct Tquery
	{
		int l,r,id;
		bool operator <(const Tquery&rhs)const{return bl[l]==bl[rhs.l]?r<rhs.r:bl[l]<bl[rhs.l];}
	}q[N];
	int calc(int x,int y){return a[x]<a[y]?x:y;}
	int query(int x,int y)
	{
		if(x>y) swap(x,y);
		int t=Log[y-x+1];
		return calc(st[t][x],st[t][y-fc[t]+1]);
	}
	void init()
	{
		fc[0]=1;for(int i=1;i<20;++i) fc[i]=fc[i-1]<<1;
		for(int i=2;i<N;++i) Log[i]=Log[i>>1]+1;
		n=read();Q=read();
		for(int i=1;i<=n;++i) a[i]=read(),bl[i]=(i-1)/lim+1,st[0][i]=i;
		for(int j=1;j<=18;++j) for(int i=1;i+fc[j]-1<=n;++i)
			st[j][i]=calc(st[j-1][i],st[j-1][i+fc[j-1]]);
		for(int i=1;i<=n;++i)
		{
			while(top && a[stk[top]]>=a[i]) R[stk[top--]]=i;
			stk[++top]=i;
		}
		while(top) R[stk[top--]]=n+1;
		for(int i=n;i;--i)
		{
			while(top && a[stk[top]]>a[i]) L[stk[top--]]=i;
			stk[++top]=i;
		}
		while(top) L[stk[top--]]=0;
		for(int i=n;i;--i) sr[i]=sr[R[i]]+1ll*(R[i]-i)*a[i];
		for(int i=1;i<=n;++i) sl[i]=sl[L[i]]+1ll*(i-L[i])*a[i];
	}
	void updatel(int l,int r,int v)
	{
		int p=query(l,r);
		res+=(1ll*a[p]*(r-p+1)+sr[l]-sr[p])*v;//wrong because 1ll*v*a[p]...
	}
	void updater(int l,int r,int v)
	{
		int p=query(l,r);
		res+=(1ll*a[p]*(p-l+1)+sl[r]-sl[p])*v;
	}
	void solve()
	{
		for(int i=1;i<=Q;++i) q[i].l=read(),q[i].r=read(),q[i].id=i;
		sort(q+1,q+Q+1);res=a[1];
		for(int l=1,r=1,i=1;i<=Q;++i)
		{
			for(;r<q[i].r;) ++r,updater(l,r,1);
			for(;l>q[i].l;) --l,updatel(l,r,1);
			for(;r>q[i].r;) updater(l,r,-1),--r;
			for(;l<q[i].l;) updatel(l,r,-1),++l;
			ans[q[i].id]=res;
		}
		for(int i=1;i<=Q;++i) writeln(ans[i]);
	}
	void solution(){init();solve();}
}

int main()
{
#ifdef Durant_Lee
	freopen("BZOJ4540.in","r",stdin);
	freopen("BZOJ4540.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值