題意:
給定一個長度爲n的數組a
a中一個連續區間的strength是區間內的最小值,對x=1,2,...,n分別求長度爲x的連續區間中,strength的最大值是多少
思路:
對於每個a[i]找出在a[i]左邊,離a[i]最近且比a[i]小的數的下標,記爲VL[i],若不存在則VL[i]=0;
找出在a[i]右邊,離a[i]最近且比a[i]小的數的下標,記爲VR[i],若不存在則VR[i]=n+1;
(VL和VR數組可以用棧計算出,具體直接見代碼)
那麼,a[i]是區間(VL[i],VR[i])內的最小值,也就是說,a[i]會出現在長度爲1,2,...,VR[i]-VL[i]-1 的組的strength中,於是就用a[i]去更新這些最大值
易知a[i]不會成爲長度大於VR[i]-VL[i]-1的區間的最小值,也就不需要更新這些最大值。
現在給定一個數列ans[1],ans[2],..,ans[n],初值爲0
對於每個a[i],讓區間[1,VR[i]-VL[i]-1]內的ans都與a[i]取最大值
要實現這個操作,可以用線段樹。
在所有操作結束後一次性下傳所有最大值標記,然後輸出最後的ans數組即可。
看題解,第二點是不需要線段樹來實現的,第二步可以做到O(n)
首先用ans[i]代表從i往左全部都與ans[i]進行max操作,這就相當於差分的思想
於是,前面第一步,直接令ans[ VR[i]-VL[i]-1 ]與a[i]進行max操作
然後倒着線性遞推一遍ans[i]=max(ans[i],ans[i+1])就可以了。
線段樹代碼(6400KB 124ms):
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#define maxn 200007
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
using namespace std;
int n;
int a[maxn];
int vl[maxn];
int vr[maxn];
stack<int> s;
int ans[maxn<<2];
void PushDown(int rt){
if(ans[rt]){
ans[rt<<1]=max(ans[rt],ans[rt<<1]);
ans[rt<<1|1]=max(ans[rt],ans[rt<<1|1]);
ans[rt]=0;
}
}
void build(int l,int r,int rt){
if(l==r){
ans[rt]=0;
return;
}
int m=(l+r)>>1;
build(ls);
build(rs);
ans[rt]=0;
}
void Add(int L,int R,int C,int l,int r,int rt){
if(L <= l && r <= R){
ans[rt]=max(ans[rt],C);
return;
}
int m=(l+r)>>1;
if(L <= m) Add(L,R,C,ls);
if(R > m) Add(L,R,C,rs);
}
void PushDownAll(int l,int r,int rt){
if(l==r) {
a[l]=ans[rt];
return;
}
int m=(l+r)>>1;
PushDown(rt);
PushDownAll(ls);
PushDownAll(rs);
}
int main(void)
{
while(~scanf("%d",&n)){
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
while(!s.empty()) s.pop();//初始化vl數組
for(int i=1;i<=n;++i){
while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
if(s.empty()) vl[i]=0;
else vl[i]=s.top();
s.push(i);
}
while(!s.empty()) s.pop();//初始化vr數組
for(int i=n;i>=1;--i){
while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
if(s.empty()) vr[i]=n+1;
else vr[i]=s.top();
s.push(i);
}
build(1,n,1);//初始化線段樹
for(int i=1;i<=n;++i){
Add(1,vr[i]-vl[i]-1,a[i],1,n,1);//操作
}
PushDownAll(1,n,1);//下傳所有標記並將結果存入a[i]
for(int i=1;i<=n;++i){//輸出答案
printf("%d",a[i]);
if(i==n) printf("\n");
else printf(" ");
}
}
return 0;
}
方法二代碼(4000KB 108ms):
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <cstring>
#define maxn 200007
using namespace std;
int n;
int a[maxn];
int vl[maxn];
int vr[maxn];
stack<int> s;
int ans[maxn];
int main(void)
{
while(~scanf("%d",&n)){
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
while(!s.empty()) s.pop();//初始化vl數組
for(int i=1;i<=n;++i){
while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
if(s.empty()) vl[i]=0;
else vl[i]=s.top();
s.push(i);
}
while(!s.empty()) s.pop();//初始化vr數組
for(int i=n;i>=1;--i){
while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
if(s.empty()) vr[i]=n+1;
else vr[i]=s.top();
s.push(i);
}
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;++i){
int maxLen=vr[i]-vl[i]-1;
ans[maxLen]=max(ans[maxLen],a[i]);
}
for(int i=n-1;i>0;--i) ans[i]=max(ans[i],ans[i+1]);
for(int i=1;i<=n;++i){//輸出答案
printf("%d",ans[i]);
if(i==n) printf("\n");
else printf(" ");
}
}
return 0;
}