WOJ 1618 - Magic Array (线段树+单调栈)
来源:互联网 发布:matlab非线性最优化 编辑:程序博客网 时间:2024/06/08 09:13
题意:给定n(n<=500000)个数,A[1],A[2],...,A[n]。求所有子区间的 (最大值*最小值*长度)之和,对 10^9 取余数。
思路一:暴力 时间复杂度O(n^3) 超时
枚举所有子区间,然后遍历一遍来求最大值和最小值,然后累加答案。
思路二:稍加优化 时间复杂度O(n^2) 超时
枚举R,然后对于每个L<=R,用Max[L]表示区间[L,R]的最大值,Min[L]表示区间[L..R]的最小值。
在R增加1之后,每个L<=旧R的Max[L]和Min[L]只需要用A[R]去更新就行了。
代码:
//Simple solveint Min[maxn],Max[maxn];int SimpleSolve(){LL ANS=0;for(int R=1;R <=n;++R){Min[R]=Max[R]=A[R];for(int L=1;L <= R;++L){//更新Min[L]和Max[L] Min[L]=min(Min[L],A[R]);Max[L]=max(Max[L],A[R]);//累加区间[L,R]的答案 ANS=(ANS+(LL)Min[L]*Max[L]%MOD*(R-L+1))%MOD;}}return ANS;}
思路三是思路二的线段树优化,所以在看思路三之前,请确保看懂了思路二。
思路二中,对于每个R,用A[R]更新了[1..R]的最大值和最小值,然后累加了[1..R]到R的答案。
如果更新和求和都使用线段树的话,时间复杂度就变成了O(n*log(n))。
首先,如何用线段树维护最大值。
对于每个R,要将Max[1..R]的数组中,所有小于A[R]的都更新成A[R]。
注意到Max[1],Max[2],...,Max[R]是单调非增的数列。
(Max[1]代表区间[1..R]的最大值,Max[2]代表区间[2..R]的最大值,明显Max[1]>=Max[2].)
所以,实际上,从某下标L开始,Max[L..R]都小于A[R],于是变成了线段树的区间修改:将[L..R]的数变成A[R]。
那么,怎么找到L值呢?在线段树的节点上多维护一个最大值的最大值,然后就可以判断了,具体看代码。
最小值同理。
然后来谈一谈线段树每个节点需要的变量,以下用m表示最小值,用M表示最大值,用L表示长度。
变量分为:标记量和统计量。
先来谈标记量,需要最小值标记,最大值标记,和长度标记,记做m,M,L。
毕竟是区间修改,所以需要三种标记。
统计量:
然后明显要有(最大值*最小值*长度)的和,记做 smML。
在修改了最大值或最小值或长度时,要能够直接更新smML这个变量,
所以需要记录(最大值*最小值)的和,(最大值*长度)的和,(最小值*长度)的和,分别记做smM,sML,smL。
然后,在修改了最大值或最小值或长度时,要能够直接更新smM,sML,smL这三个变量,
需要记录最小值之和,最大值之和,长度之和,分别记做sm,sM,sL。
于是,s开头的求和的统计量需要7个。
在增加一个线段树区间的长度时,要更新sL,需要sL加上该区间的长度,所以用变量n表示该线段树区间的长度。
为了在更新最大值的时候可以找到左边界,需要记录最大值的最大值,记为MM。
同理,记录最小值的最小值,记为mm。
于是,线段树的一个节点,需要3个标记量 和 10个统计量。
节点的定义见下面代码:
//Segment Tree Nodestruct Node{int m,M,L;//min max Len 3个标记量int mm,MM,n;//min of min,max of max , number of intervalsint sm,sM,sL,smM,smL,sML,smML;//sum of productsNode(){m=M=L=0;}Node operator+(const Node &B){//节点的统计量的更新Node &A = *this,C;C.mm = min(A.mm,B.mm);C.MM = max(A.MM,B.MM);C.n = A.n + B.n;C.sm = (A.sm + B.sm ) % MOD;C.sM = (A.sM + B.sM ) % MOD;C.sL = (A.sL + B.sL ) % MOD;C.smM = (A.smM + B.smM ) % MOD;C.smL = (A.smL + B.smL ) % MOD;C.sML = (A.sML + B.sML ) % MOD;C.smML = (A.smML + B.smML) % MOD;return C;}void SetMax(int Max){//将该区间的最大值改为Max,修改最大值标记,以及对应的统计量M = MM = Max;sM = (LL)n * M % MOD;// 最大值的和 = 数量 * 最大值smM = (LL)sm * M % MOD;//(最小值*最大值)的和 = 最小值的和 * 最大值sML = (LL)sL * M % MOD;//(最大值*长度)的和 = 长度的和 * 最大值smML = (LL)smL * M % MOD;//(最大值*最小值*长度)的和 = (最小值*长度)的和 * 最大值}void SetMin(int Min){//将该区间的最小值改为Min,修改最小值标记,以及对应的统计量m = mm = Min;sm = (LL)n * m % MOD;smM = (LL)sM * m % MOD;smL = (LL)sL * m % MOD;smML = (LL)sML * m % MOD;}void AddLen(LL k){//该区间的长度增加kL += k;sL = (sL + k*n )%MOD ;smL = (smL + k*sm )%MOD ;sML = (sML + k*sM )%MOD ;smML = (smML + k*smM)%MOD;}void SetValue(LL V){//设置叶节点的值sL=n=1;smL=sML=sm=sM=mm=MM=V;smML=smM=V*V%MOD;}};
还剩下最大值的更新的左端点怎么找的问题:
首先,要在[1..R]这个区间中,将所有的比A[R]小的值变成A[R]。
第一部分:常规的线段树区间判断,可以得到所有在[1..R]之内的区间。
第二部分:如果本区间的最大值小于等于V,直接将整个区间的最大值设置为V即可。
如果不是叶节点,那么进行递归调用,右区间一定要递归调用,左区间根据条件。
如果右区间的最大值小于等于V,那么左侧可能也有要更新的区间。所以要递归调用左侧。
具体见UpdateMax函数:
void UpdateMax(int X,int V,int l,int r,int rt){//[1,X]int L = rt << 1 , R = rt << 1 | 1 , m = (l + r) >> 1;if(r <= X){//第二部分,得到了[1..X]的区间之后 if(D[rt].MM <= V){//如果本区间的最大值小于等于V,直接将本区间的最大值设置为V D[rt].SetMax(V);return;}if(l==r) return;//如果是叶节点,直接返回 PushDown(rt);UpdateMax(X,V,rs);//更新右侧 //如果右侧的最大值大于V,那么左侧不可能有需要更新的值,所以不需要递归左侧//否则,需要递归左侧 if(D[R].MM <= V) UpdateMax(X,V,ls); PushUp(rt);return;}//第一部分:常规线段树区间判断,可以得到所有[1..X]之内的区间 PushDown(rt);UpdateMax(X,V,ls);if(X > m) UpdateMax(X,V,rs);PushUp(rt);}
最后总结一下:
空间的问题:50万数据量,需要的线段树元素个数是1048576个,要是按一般的做法,直接四倍的话,就超出空间范围了。
时间的问题:开始写的是,先用线段树搜索出更新最大值的左侧下标L,再区间修改[L,R],然后超时了。
后来改成了在修改的时候顺便寻找修改边界,就快了许多。
----------------------------------------------------------------------------- 分割线 ------------------------------------------------------------------
上面说的方法,用时9.4秒。经过三个优化之后,可以达到1.5秒。
优化一:可以发现,对于每个R,如果A[R]>A[R-1]那么只需要更新最大值数组,最小值都不需要更新。节省了一半的线段树操作。
优化二:对于L值,前面的做法是把它当做数据来维护,其实不需要。可以直接在Query函数中计算,省掉了更新长度的操作,以及长度的懒惰标记。
优化三:前面的做法是在更新最大值的时候,利用统计量MM来找到左边界。其实可以用单调栈直接维护左边界。省去了左边界判断时间,以及mm,MM两个变量。
第一份代码如下:
/*Problem 1618 - Magic Array 56520KB 9420ms */ #include <iostream>#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#define LL long long#define MOD 1000000000#define maxn 500007#define ls l,m,rt<<1#define rs m+1,r,rt<<1|1using namespace std;//Inputint n,A[maxn];//Segment Tree Nodestruct Node{int m,M,L;//min max Lenint mm,MM,n;//min of min,max of max , number of intervalsint sm,sM,sL,smM,smL,sML,smML;//sum of productsNode(){m=M=L=0;}Node operator+(const Node &B){Node &A = *this,C;C.mm = min(A.mm,B.mm);C.MM = max(A.MM,B.MM);C.n = A.n + B.n;C.sm = (A.sm + B.sm ) % MOD;C.sM = (A.sM + B.sM ) % MOD;C.sL = (A.sL + B.sL ) % MOD;C.smM = (A.smM + B.smM ) % MOD;C.smL = (A.smL + B.smL ) % MOD;C.sML = (A.sML + B.sML ) % MOD;C.smML = (A.smML + B.smML) % MOD;return C;}void SetMax(int Max){M = MM = Max;sM = (LL)n * M % MOD;smM = (LL)sm * M % MOD;sML = (LL)sL * M % MOD;smML = (LL)smL * M % MOD;}void SetMin(int Min){m = mm = Min;sm = (LL)n * m % MOD;smM = (LL)sM * m % MOD;smL = (LL)sL * m % MOD;smML = (LL)sML * m % MOD;}void AddLen(LL k){L += k;sL = (sL + k*n )%MOD ;smL = (smL + k*sm )%MOD ;sML = (sML + k*sM )%MOD ;smML = (smML + k*smM)%MOD;}void SetValue(LL V){sL=n=1;smL=sML=sm=sM=mm=MM=V;smML=smM=V*V%MOD;}}D[1048576];void PushUp(int rt){D[rt] = D[rt<<1] + D[rt<<1|1];}void PushDown(int rt){//Push down three marksint L = rt << 1 , R = rt << 1 | 1;if(D[rt].M){D[L].SetMax(D[rt].M);D[R].SetMax(D[rt].M);D[rt].M=0;}if(D[rt].m){D[L].SetMin(D[rt].m);D[R].SetMin(D[rt].m);D[rt].m=0;}if(D[rt].L){D[L].AddLen(D[rt].L);D[R].AddLen(D[rt].L);D[rt].L=0;}}void Build(int l,int r,int rt){if(l==r){D[rt].SetValue(A[l]);return;}int m=(l+r)>>1;Build(ls);Build(rs);PushUp(rt);}void UpdateMax(int X,int V,int l,int r,int rt){//[1,X]int L = rt << 1 , R = rt << 1 | 1 , m = (l + r) >> 1;if(r <= X){if(D[rt].MM <= V){D[rt].SetMax(V);return;}if(l==r) return;PushDown(rt);UpdateMax(X,V,rs);if(D[R].MM <= V) UpdateMax(X,V,ls); PushUp(rt);return;}PushDown(rt);UpdateMax(X,V,ls);if(X > m) UpdateMax(X,V,rs);PushUp(rt);}void UpdateMin(int X,int V,int l,int r,int rt){//[1,X]int L = rt << 1 , R = rt << 1 | 1 , m = (l + r) >> 1;if(r <= X){if(D[rt].mm >= V){D[rt].SetMin(V);return;}if(l==r) return;PushDown(rt);UpdateMin(X,V,rs);if(D[R].mm >= V) UpdateMin(X,V,ls); PushUp(rt);return;}PushDown(rt);UpdateMin(X,V,ls);if(X > m) UpdateMin(X,V,rs);PushUp(rt);}void UpdateLen(int X,int l,int r,int rt){//[1,X] if(r <= X){D[rt].AddLen(1);return;}PushDown(rt);int m = (l + r) >> 1;UpdateLen(X,ls);if(X > m) UpdateLen(X,rs);PushUp(rt);}LL Query(int X,int l,int r,int rt){//求和if(r <= X){return D[rt].smML;}PushDown(rt);int m = (l + r) >> 1;LL ANS = Query(X,ls);if(X > m) ANS = (ANS + Query(X,rs)) % MOD;return ANS;}int main(void){while(~scanf("%d",&n)){for(int i=1;i<=n;++i) scanf("%d",&A[i]);Build(1,n,1);LL ANS = Query(1,1,n,1);for(int R=2;R <= n;++R){UpdateMax(R,A[R],1,n,1);//更新最大值UpdateMin(R,A[R],1,n,1);//更新最小值UpdateLen(R-1,1,n,1);//更新长度ANS = (ANS + Query(R,1,n,1)) % MOD;//累加答案}printf("%d\n",(int)ANS);}return 0;}
优化后代码如下:
/*Problem 1618 - Magic Array Memory: 44260KB Time: 1500ms*/ #include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #define LL long long #define MOD 1000000000 #define maxn 500007 #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 using namespace std;//Input int n,A[maxn]; int Min[maxn],IMin;int Max[maxn],IMax;//Segment Tree Node struct Node{ int m,M;//min max int n;//number of intervals int sm,sM,sL,smM,smL,sML,smML;//sum of products Node(){m=M=0;} Node operator+(const Node &B)const{ const Node &A = *this; Node C; C.n = A.n + B.n; C.sm = (A.sm + B.sm ) % MOD; C.sM = (A.sM + B.sM ) % MOD; C.smM = (A.smM + B.smM ) % MOD; C.sL = (A.sL + (LL)A.n * B.n + B.sL ) % MOD; C.smL = (A.smL + (LL)A.sm * B.n + B.smL ) % MOD; C.sML = (A.sML + (LL)A.sM * B.n + B.sML ) % MOD; C.smML = (A.smML + (LL)A.smM * B.n + B.smML) % MOD; return C; } void SetMax(int Max){ M = Max; sM = (LL)n * M % MOD; smM = (LL)sm * M % MOD; sML = (LL)sL * M % MOD; smML = (LL)smL * M % MOD; } void SetMin(int Min){ m = Min; sm = (LL)n * m % MOD; smM = (LL)sM * m % MOD; smL = (LL)sL * m % MOD; smML = (LL)sML * m % MOD; } void SetValue(LL V){ sL=n=1; smL=sML=sm=sM=V; smML=smM=V*V%MOD; } }D[1048576];void PushUp(int rt){D[rt] = D[rt<<1] + D[rt<<1|1];}void PushDown(int rt){//Push down three marks int L = rt << 1 , R = rt << 1 | 1; if(D[rt].M){ D[L].SetMax(D[rt].M); D[R].SetMax(D[rt].M); D[rt].M=0; } if(D[rt].m){ D[L].SetMin(D[rt].m); D[R].SetMin(D[rt].m); D[rt].m=0; } }void Build(int l,int r,int rt){ if(l==r){ D[rt].SetValue(A[l]); return; } int m=(l+r)>>1; Build(ls); Build(rs); PushUp(rt); } void UpdateMax(int L,int R,int V,int l,int r,int rt){if(L <= l && r <= R){D[rt].SetMax(V);return;}PushDown(rt);int m = (l + r) >> 1;if(L <= m) UpdateMax(L,R,V,ls);if(R > m) UpdateMax(L,R,V,rs);PushUp(rt);}void UpdateMin(int L,int R,int V,int l,int r,int rt){if(L <= l && r <= R){D[rt].SetMin(V);return;}PushDown(rt);int m = (l + r) >> 1;if(L <= m) UpdateMin(L,R,V,ls);if(R > m) UpdateMin(L,R,V,rs);PushUp(rt);} Node Query(int X,int l,int r,int rt){ if(r <= X){ return D[rt]; } PushDown(rt); int m = (l + r) >> 1; Node ANS = Query(X,ls); if(X > m) ANS = ANS + Query(X,rs); return ANS; }int main(void){ while(~scanf("%d",&n)){ for(int i=1;i<=n;++i) scanf("%d",&A[i]); Build(1,n,1); Min[0]=Max[0]=IMin=IMax=0; LL ANS = 0; for(int R=1;R <= n;++R){ while(IMin && A[R]<=A[Min[IMin]]) --IMin;Min[++IMin]=R;while(IMax && A[R]>=A[Max[IMax]]) --IMax;Max[++IMax]=R; if(A[R]>A[R-1]) UpdateMax(Max[IMax-1]+1,R,A[R],1,n,1); else UpdateMin(Min[IMin-1]+1,R,A[R],1,n,1); ANS = (ANS + Query(R,1,n,1).smML) % MOD; } printf("%d\n",(int)ANS); } return 0;}
- WOJ 1618 - Magic Array (线段树+单调栈)
- HDU3183 A Magic Lamp —— 贪心(单调队列优化)/ RMQ / 线段树
- [BZOJ3956]Count(单调栈+线段树)
- [BZOJ3956]Count(单调栈+线段树)
- bzoj3956 -- 单调栈 + 线段树
- Devour Magic 线段树
- WOJ 641 Events 线段树解法
- HDU 3872 Dragon Ball(DP+线段树+单调栈)
- bzoj1012 [JSOI2008]最大数(单调栈+二分/线段树)
- BZOJ4826:[Hnoi2017]影魔 (单调栈+扫描线+线段树)
- bzoj 1012 最大数(线段树|单调队列|单调栈)
- BZOJ 1012 线段树或单调栈
- [BZOJ4826][HNOI2017]影魔-单调栈-线段树
- God Knows 线段树维护单调栈
- 【技巧】线段树维护区间单调栈
- 物理( 线段树套单调队列 )
- bzoj1012(线段树或单调队列)
- (简单)线段树 HOJ 2965 Magic-Pen4
- redis集群 部署操作流程
- Android 百度地图 SDK v3.0.0 (一)
- hdu1398Square Coins(母函数)
- 断点续传的原理剖析与讲解
- Android中的onClick事件
- WOJ 1618 - Magic Array (线段树+单调栈)
- ViewPager 循环播放广告
- c/c++ 读取当前时间
- HOG特征
- webstorm 连接mysql
- SpriteKit中类似Cocos2D的CCActionSpawn并发方法GroupAction
- MD5校验碰撞的概率是多少?
- CQOI2016 K远点对 计算几何
- 关于Extjs的提交问题 success : function(form, action)