C2. Skyscrapers (hard version)
有n栋楼,每栋楼最高为hi,不允许存在一栋楼两边都有比他更高的楼,要求建成的楼的总高度最大。
延续C1的想法,我们要找到一个最高点,然后向左向右全部都向低处扩展。这个最高点将序列分成了两个部分,从左到右是非递减序列,从右到左是非递减序列。那么我们利用单调栈的思想,求出每个数向左所影响的区间和向右所影响的区间,分别从左往右和从右往左求一次单调栈即可。然后在求的过程中维护前缀楼高和后缀楼高。详细见代码。
#include
using namespace std;
typedef long long LL;
const int N=5e5+10;
LL m[N];
LL l[N],r[N];
stack<LL>st;//单调栈
int main()
{
LL n;
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&m[i]);
for(int i=1;i<=n;i++)
{
while(!st.empty()&&m[i]<m[st.top()])
st.pop();
if(st.empty()) l[i]=(LL)i*m[i];
else l[i]=(LL)(i-st.top())*m[i]+l[st.top()];
st.push(i);
}
while(!st.empty()) st.pop();
for(int i=n;i>=1;i--)
{
while(!st.empty()&&m[i]<m[st.top()])
st.pop();
if(st.empty()) r[i]=(n-i+1)*m[i];
else r[i]=(st.top()-i)*m[i]+r[st.top()];
st.push(i);
}
LL maxn=0,idx=0;
for(int i=1;i<=n;i++)
{
if(l[i]+r[i]-m[i]>maxn)
{
maxn=l[i]+r[i]-m[i];
idx=i;
}
}
LL ans=m[idx];
for(int i=idx;i>=1;i--)
{
ans=min(ans,m[i]);
m[i]=ans;
}
ans=m[idx];
for(int i=idx+1;i<=n;i++)
{
ans=min(ans,m[i]);
m[i]=ans;
}
for(int i=1;i<=n;i++) printf("%lld ",m[i]);
//system("pause");
return 0;
}
考虑分治,solve(l,r)代表当前处理到[l,r]这个区间。假设区间的最小值是m[mid],那么必然要把区间[l,mid-1]或者[mid+1,r]里的元素都变为m[mid],否则不符合题意。然后区间最小值用线段树查找即可。最后的时间复杂度是nlogn。
#include
using namespace std;
typedef long long LL;
const int N=5e5+10;
LL m[N],n;
struct node
{
int l,r;
pair<LL,LL>pii;
}tr[N*4];
LL ans=0,idx=0;
void pushup(int x)
{
if(tr[x<<1].pii.second<=tr[x<<1|1].pii.second)
{
tr[x].pii.first=tr[x<<1].pii.first;
tr[x].pii.second=tr[x<<1].pii.second;
}
else
{
tr[x].pii.first=tr[x<<1|1].pii.first;
tr[x].pii.second=tr[x<<1|1].pii.second;
}
return ;
}
void build(int k,int l,int r)
{
tr[k].l=l,tr[k].r=r;
if(l==r)
{
tr[k].pii.first=l,tr[k].pii.second=m[l];
return ;
}
int mid=(l+r)/2;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(k);
}
inline pair<LL,LL> query(int k,int l,int r) //区间查询最小值
{
if(tr[k].l>=l&&tr[k].r<=r) return tr[k].pii;
int mid=(tr[k].l+tr[k].r)/2;
pair<LL,LL>maxn;
maxn.first=1e9+10,maxn.second=1e9+10;
if(l<=mid) maxn=query(k<<1,l,r);
if(r>mid)
{
pair<LL,LL>p=query(k<<1|1,l,r);
if(p.second<maxn.second) maxn=p;
}
return maxn;
}
void solve(int l,int r,LL sum)
{
if(l==r)
{
sum=sum+m[l];
if(sum>ans)
{
ans=sum;
idx=l;
}
return ;
}
pair<LL,LL>now=query(1,l,r);
LL mid=now.first;
if(mid+1<=r) solve(mid+1,r,sum+now.second*(mid-l+1));
if(mid-1>=l) solve(l,mid-1,sum+now.second*(r-mid+1));
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&m[i]);
build(1,1,n);
solve(1,n,0);
LL k=m[idx];
for(int i=idx;i>=1;i--)
{
k=min(k,m[i]);
m[i]=k;
}
k=m[idx];
for(int i=idx+1;i<=n;i++)
{
k=min(k,m[i]);
m[i]=k;
}
for(int i=1;i<=n;i++) printf("%lld ",m[i]);
//system("pause");
return 0;
}
不管是时间复杂度还是代码的好写度来说都是第一种方法更加优秀。