2019牛客多校训练第四场C.sequence(线段树+单调栈+前缀和)
题意
输入整数n,给出两个包含n个整数的序列a和b,找到一个区间[l,r],使在该区间内a序列最小值×b序列区间和的值最大。
题解
遍历a序列,维护单调栈求以当前值a[i]为最小值时的最大可达区间[ L[i],R[i] ](即最大左右边界);
b序列先预处理其前缀和pre_sum,后用线段树维护pre_sum数组的区间最值;所以答案就是a[i]×b序列在[ L[i],R[i] ]区间内的和的最大值,不断更新最优解求最大结果即可。
当a[i]≥0时,b序列在[ L[i],R[i] ]区间内的和的最大值为b序列前缀和数组后半部分最大值-前半部分最小值,即:
max( [pre_sum(i),pre_sum(R[i])] )-min( [pre_sum(L[i]-1),pre_sum(i-1)] );
当a[i]<0时,b序列在[ L[i],R[i] ]区间内的和的最大值为b序列前缀和数组后半部分最小值-前半部分最大值,即:
min( [pre_sum(i),pre_sum(R[i])] )-max( [pre_sum(L[i]-1),pre_sum(i-1)] );
时间复杂度O(nlogn)。
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /*2442ms*/ 2 #include<bits/stdc++.h> 3 using namespace std; 4 typedef long long ll; 5 const int maxn=3e6+5; 6 const ll inf=0x3f3f3f3f3f3f3f3fLL; 7 ll a[maxn],b[maxn],pre_sum[maxn],L[maxn],R[maxn],sta[maxn]; 8 struct node 9 { 10 int l,r;//区间[l,r] 11 ll mx;//区间最大值 12 ll mn;//区间最小值 13 }tree[maxn<<2];//一定要开到4倍多的空间 14 void pushup(int index) 15 { 16 tree[index].mx=max(tree[index<<1].mx,tree[index<<1|1].mx); 17 tree[index].mn=min(tree[index<<1].mn,tree[index<<1|1].mn); 18 } 19 void build(int l,int r,int index) 20 { 21 tree[index].l=l; 22 tree[index].r=r; 23 if(l==r){ 24 tree[index].mn=tree[index].mx=pre_sum[l]; 25 return; 26 } 27 int mid=(l+r)>>1; 28 build(l,mid,index<<1); 29 build(mid+1,r,index<<1|1); 30 pushup(index); 31 } 32 ll queryMIN(int l,int r,int index) 33 { 34 if(l<=tree[index].l&&r>=tree[index].r) 35 return tree[index].mn; 36 int mid=(tree[index].l+tree[index].r)>>1; 37 ll Min=inf; 38 if(l<=mid) 39 Min=min(queryMIN(l,r,index<<1),Min); 40 if(r>mid) 41 Min=min(queryMIN(l,r,index<<1|1),Min); 42 return Min; 43 } 44 ll queryMAX(int l,int r,int index) 45 { 46 if(l<=tree[index].l&&r>=tree[index].r) 47 return tree[index].mx; 48 int mid=(tree[index].l+tree[index].r)>>1; 49 ll Max=-inf; 50 if(l<=mid) 51 Max = max(queryMAX(l,r,index<<1),Max); 52 if(r>mid) 53 Max = max(queryMAX(l,r,index<<1|1),Max); 54 return Max; 55 } 56 int main() 57 { 58 int n; 59 while(~scanf("%d",&n)) 60 { 61 for(int i=1;i<=n;i++) 62 scanf("%lld",&a[i]); 63 for(int i=1;i<=n;i++){ 64 scanf("%lld",&b[i]); 65 pre_sum[i]=pre_sum[i-1]+b[i];//求b序列前缀和 66 } 67 build(0,n,1); 68 /*单调栈求左端点L[i]*/ 69 int top=0; 70 for(int i=1;i<=n;i++){ 71 while(top&&a[i]<=a[sta[top]]) 72 top--; 73 L[i]=(top==0)?1:sta[top]+1; 74 sta[++top]=i; 75 } 76 /*单调栈求右端点R[i]*/ 77 top=0; 78 for(int i=n;i>=1;i--){ 79 while(top&&a[i]<=a[sta[top]]) 80 top--; 81 R[i]=(top==0)?n:sta[top]-1; 82 sta[++top]=i; 83 } 84 85 ll ans=-inf; 86 for(int i=1;i<=n;i++){ 87 ll cnt=0; 88 if(a[i]>0){ 89 cnt=(queryMAX(i,R[i],1)-queryMIN(L[i]-1,i-1,1))*a[i]; 90 } 91 else if(a[i]<0){ 92 cnt=(queryMIN(i,R[i],1)-queryMAX(L[i]-1,i-1,1))*a[i]; 93 } 94 else if(a[i]==0) 95 cnt=0; 96 if(cnt>ans)ans=cnt; 97 } 98 printf("%lld\n",ans); 99 } 100 return 0; 101 } 102 103 /* 104 7 105 -519 9794 1664 8189 -295 3471 1104 106 -1796 -3137 7098 -3333 4683 -3968 467 3 107 11811072 108 */ 109 /* 110 18 111 -273 7169 -8145 -6664 -2970 698 8701 -6791 459 -5678 -846 8072 6384 5051 -3606 -4737 -9928 6815 112 1609 -8810 9205 -9129 -1919 -116 -6752 385 4347 614 6261 4399 -8961 1022 -1360 -5074 7825 -122 113 142708545 114 */