思路
假设现在从第个点出发,定义如下:
简单来说,就是在左边找到第一个高于的楼(高度为),若继续往左仍有高度为的楼且该楼与之间没有更高的楼阻挡,就继续往左取。对称地定义。
那么最优解就是在中取较矮的楼转移,若二者高度相同,则都进行转移,在得到的答案中取最大值。简单的证明:若先转移到较高的楼就失去了向较矮的楼转移获得更大答案的机会,而先转移到较矮的楼仍然可以向原先较高的楼转移,所以是最优的。转移的过程可以使用记忆化搜索。
如何在搜索中得到决定了该题的复杂度。首先用单调栈预处理出两个数组和。为左侧第一个高于或等于当前楼的位置,为右侧第一个高于或等于当前楼的位置。在搜索的时候就可以借助这两个数组顺藤摸瓜一路找到和。复杂度可能比较玄学,但是能过。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
using namespace std;
const int N=1E5+10;
const int long long INF=0x7ffffffffff;
typedef long long ll;
int n;
ll a[N],pre[N],nxt[N];;
ll ans[N];
stack<ll> s;
ll dfs(ll cur)
{
if(ans[cur]!=-INF)
return ans[cur];
ans[cur]=0;
ll lp=pre[cur],rp=nxt[cur];
while(lp && (a[lp]==a[cur] || a[lp]==a[pre[lp]]))
lp=pre[lp];
while(rp && (a[rp]==a[cur] || a[rp]==a[nxt[rp]]))
rp=nxt[rp];
if(a[rp]<a[lp])
{
ans[cur]=rp-cur;
ans[cur]+=dfs(rp);
}
else if(a[rp]>a[lp])
{
ans[cur]=cur-lp;
ans[cur]+=dfs(lp);
}
else if(lp)
ans[cur]=max(dfs(lp)+cur-lp,dfs(rp)+rp-cur);
return ans[cur];
}
int main()
{
scanf("%d",&n);
rep(i,1,n)
scanf("%lld",a+i);
rep(i,0,n)
ans[i]=-INF;
rep(i,1,n)
{
int j=i;
while(a[i]==a[j+1])
++j;
while(!s.empty() && a[i]>=a[s.top()])
{
nxt[s.top()]=j;
s.pop();
}
rep(k,i,j)
s.push(k);
i=j;
}
while(!s.empty())
{
nxt[s.top()]=0;
s.pop();
}
for(int i=n;i>=1;--i)
{
int j=i;
while(a[i]==a[j-1])
--j;
while(!s.empty() && a[i]>=a[s.top()])
{
pre[s.top()]=j;
s.pop();
}
rep(k,j,i)
s.push(k);
i=j;
}
while(!s.empty())
{
pre[s.top()]=0;
s.pop();
}
// rep(i,1,n)
// cout<<pre[i]<<" "<<nxt[i]<<endl;
a[0]=INF;
rep(i,1,n)
{
if(ans[i]!=-INF)
continue;
ans[i]=dfs(i);
}
rep(i,1,n)
printf("%lld ",ans[i]);
printf("\n");
}