【BZOJ 3745】Norma(CDQ分治)

题目链接

题目描述

求:
∑ i = 1 n ∑ j = i n ( j − i + 1 ) m i n ( a i , a i + 1 , … a j )   m a x ( a i , a i + 1 , … a j ) \sum_{i=1}^{n}\sum_{j=i}^{n} (j-i+1)min(a_i,a_{i+1}, \dots a_j)\ max(a_i,a_{i+1}, \dots a_j) i=1nj=in(ji+1)min(ai,ai+1,aj) max(ai,ai+1,aj)

Sol

经典的序列分治(CDQ分治)题 , 很有思考价值

算法主题自然是分治

把区间分成两半后 , 只考虑跨过中点的区间

我们需要一种能很好求出 m i n , m a x min,max min,max的方法

不妨先把情况分一下类:

  1. min 和 max 都在左边
  2. min 和 max都在右边
  3. min 在左而 max 在右
  4. max 在左而 min 在右

显然其实只有 min 和 max 是不是在同一侧的两种本质不同的情况 , 因为 min 和 max 是等价处理的

我们先考虑枚举一个左端点(或右端点) i i i

假设 min和max 在同一侧, 这个就很好办了 , 不管它在左在右 , 我们都可以维护一个单调指针来得到当前这一边的 min 和 max

由于不能和另一边产生冲突 , 另一边也要维护两个单调指针 , 这样就能找出使得 min max 都在同一侧的区间了 , 直接等差数列求和公式即可

而对于不在同一侧的 , 我们先写一下式子:
这里只讨论 min 在左而 max在右的情况 , 另一种一样的做法
a n s [ i ] = ∑ j = m i d + 1 r ( j − i + 1 ) ∗ m i n ( a [ i ] ∼ a [ m i d ] ) ∗ m a x ( a [ m i d + 1 ] ∼ a [ j ] ) ans[i]=\sum_{j=mid+1}^r (j-i+1) * min(a[i]\sim a[mid]) *max(a[mid+1]\sim a[j]) ans[i]=j=mid+1r(ji+1)min(a[i]a[mid])max(a[mid+1]a[j])
我们可以通过单调指针得出 和 i i i 有关的min , 记为 M i n i Min_i Mini ,而后面的记为 M a x j Max_j Maxj

a n s [ i ] = M i n i ∑ j = m i d + 1 r ( j + 1 ) ∗ M a x j − i ∗ M a x j ans[i]=Min_i \sum_{j=mid+1}^{r} (j+1)*Max_j-i*Max_j ans[i]=Minij=mid+1r(j+1)MaxjiMaxj
对于每一个 i i i合法的该种情况的区间一定是单调且连续的

于是维护一下 ( j + 1 ) ∗ M a x j (j+1)*Max_j (j+1)Maxj M a x j Max_j Maxj 的后缀和就行了

代码:

#include
#include
#include
#include
#include
#include
#include
using namespace std;
inline int read()
{
	int x=0;char ch=getchar();int t=1;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') t=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
	return x*t;
}
typedef long long ll;
const int N=5e5+10;
const int mod=1e9;
int n,a[N],maxq[N],maxn[N],maxs[N],minn[N],minq[N],mins[N];
inline void upd(int &x,int y){x+=y;if(x>=mod) x-=mod;}
int ans=0;

inline ll SUM(int l,int r,int num){return 1ll*(l+r)*num/2%mod;}

void Divide(int l,int r)
{
	if(l==r) return upd(ans,1ll*a[l]*a[l]%mod);
	register int mid=l+r>>1;
	Divide(l,mid);Divide(mid+1,r);
	register int p=mid+1,q=mid+1;
	register int Min=a[mid],Max=a[mid];
	maxn[mid+1]=minn[mid+1]=a[mid+1];
	for(register int i=mid+2;i<=r;++i) maxn[i]=max(maxn[i-1],a[i]),minn[i]=min(minn[i-1],a[i]);
	maxq[r+1]=maxs[r+1]=minq[r+1]=mins[r+1]=0;
	for(register int i=r;i>mid;--i) {
		maxq[i]=mod-maxn[i];maxs[i]=1ll*(i+1)*maxn[i]%mod;
		upd(maxq[i],maxq[i+1]);upd(maxs[i],maxs[i+1]);

		minq[i]=mod-minn[i];mins[i]=1ll*(i+1)*minn[i]%mod;
		upd(minq[i],minq[i+1]);upd(mins[i],mins[i+1]);
	}
	
	for(register int i=mid;i>=l;--i){
		Min=min(Min,a[i]);Max=max(Max,a[i]);
		while(p<=r&&a[p]<=Max) ++p;
		while(q<=r&&a[q]>=Min) ++q;
		register int pos=min(p,q)-1;
		register ll D=1ll*Max*Min%mod;
		upd(ans,D*SUM(mid-i+2,pos-i+1,pos-mid)%mod);
		if(p<q) {
			register ll S=1ll*Min*(((maxs[p]-maxs[q]+mod)%mod+1ll*i*(maxq[p]-maxq[q]+mod)%mod)%mod)%mod;
			upd(ans,S);
		}
		else if(q<p){
			register ll S=1ll*Max*(((mins[q]-mins[p]+mod)%mod+1ll*i*(minq[q]-minq[p]+mod)%mod)%mod)%mod;
			upd(ans,S);
		}
	}
	Min=a[mid+1],Max=a[mid+1];p=q=mid;
	for(register int i=mid+1;i<=r;++i){
		Min=min(Min,a[i]);Max=max(Max,a[i]);
		while(p>=l&&a[p]<Max) --p;
		while(q>=l&&a[q]>Min) --q;
		register int pos=max(p,q)+1;
		register ll D=1ll*Max*Min%mod;
		upd(ans,D*SUM(i-mid+1,i-pos+1,mid+1-pos)%mod);
	}
	return;
}
int main()
{
	n=read();
	for(register int i=1;i<=n;++i) a[i]=read();
	Divide(1,n);
	printf("%d\n",ans);
}

你可能感兴趣的:(======题解======,CDQ分治,——分治——)