题面
题意
有一串由矩形组成的图,先要求出这幅图中所能容纳的最大矩形的面积
总体思路
求出以各个小矩形高度为宽所能达到的最大面积,此时我们称这个最大矩形被这个小矩形统治,我们只需求出每一个小矩形统治的最大矩形的左端点和右端点,也就是一串数中每个数左右两边第一个比他小的数的位置.
法1(单调栈)
方法解释
让矩形依次入栈,当入栈矩形小于栈顶时,说明此时的栈顶不能统治该矩形,其右端点即为入栈矩形的位置-1.最后将让元素全部出栈,它们的右端点均为这个图的右端点
当矩形入栈后,其下面的矩形必然小于等于它,若小于,则其左端点为它下面那个矩形的位置+1,若等于,则其左端点为其下面矩形的左端点.第一个元素的左端点为整个图的左端点.
代码
#include<bits/stdc++.h>
#define P pair<int,int>
#define N 100100
using namespace std;
int n,T,TT;
int get(vector <int> a)
{
int i,k,le[N],ri[N],mx=0;
P tmp;
stack<P> st;
le[0]=0;
a.push_back(0);
tmp.first=tmp.second=0;
st.push(tmp);
for(i=1;i<=n+1;i++)
{
k=st.top().first;
while(a[i]<k&&!st.empty())
{
ri[st.top().second]=i-1;
st.pop();
k=st.top().first;
}
if(a[i]==k)
le[i]=le[st.top().second];
else
le[i]=st.top().second+1;
tmp.first=a[i];
tmp.second=i;
st.push(tmp);
}
for(i=1;i<=n;i++)
{
mx=max(a[i]*(ri[i]-le[i]+1),mx);
}
return mx;
}
int main()
{
int i,j,p,mx=0;
vector<int> a;
cin>>T;
TT=T;
while(T--)
{
a.clear();
scanf("%d",&n);
// cout<<n;
a.push_back(0);
for(i=1;i<=n;i++)
{
scanf("%d",&p);
a.push_back(p);
}
// get(a);
printf("Case %d: %d\n",TT-T,get(a));
}
}
法2
方法解释
求左端点:从小到大枚举,若每个矩形左端点的初始值为自身,若它小于等于它左端点减一的矩形的左端点,则将其左端点更新为那个矩形的左端点,如此循环更新直到大于那个矩形或为一时停止,每一个点都这样操作一遍即可.
求右端点:从大到小枚举,与左端点相同
此方法每一次操作都将两块连起来,类似并查集,故复杂度为O(n).
代码
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int a[N],n,le[N],ri[N],mx,T,TT;
int main()
{
int i,j;
cin>>T;
TT=T;
while(T--)
{
mx=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
le[i]=i;
while(le[i]>1&&a[i]<=a[le[i]-1])
{
le[i]=le[le[i]-1];
}
}
for(i=n;i>=1;i--)
{
ri[i]=i;
while(ri[i]<n&&a[i]<=a[ri[i]+1])
{
ri[i]=ri[ri[i]+1];
}
}
for(i=1;i<=n;i++)
{
mx=max(a[i]*(ri[i]-le[i]+1),mx);
}
printf("Case %d: %d\n",TT-T,mx);
}
}
法3
方法解释
首先用
n
∗
l
o
g
(
n
)
n*log(n)
n∗log(n)处理出以一个点为左端点,
2
i
2^{i}
2i为长度的一段数最小值,处理和查找时记得记录位置。
处理方法为:长度为
2
i
2^{i}
2i的最小值为两个长度为
2
i
−
1
2^{i-1}
2i−1的较小值,以此递推下去。
查找方法:见下图:
两个红色部分的最小值即为这一段的最小值
预处理之后,每次找到最小值的位置,以此计算它统治的矩形来更新答案,并以它为中心分成两段,分别继续查找。因为每个点都被查了一次,故复杂度为O(n).
代码
#include<bits/stdc++.h>
#define N 50010
#define P pair<int,int>
using namespace std;
int num[N],n,dp[N][100],k[N][100],T,TT,l,r,ans;
int log(int u)
{
int res=0;
while(u)
{
res++;
u>>=1;
}
return res;
}
int ask(int u,int v)
{
int len;
len=v-u+1;
len=log(len)-1;
if(dp[u][len]<dp[v-(1 << len)+1][len])
{
return k[u][len];
}
else
{
return k[v-(1 <<len)+1][len];
}
}
void find(int u,int r)
{
if(u>r) return;
int i,tmp;
tmp=ask(u,r);
ans=max(ans,num[tmp]*(r-u+1));
find(u,tmp-1);
find(tmp+1,r);
}
int main()
{
cin>>T;
TT=T;
while(T--)
{
int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&num[i]);
dp[i][0]=num[i];
k[i][0]=i;
}
for(i=1;(1 << i)<=n;i++)
{
for(j=1;j+(1 << i)-1<=n;j++)
{
if(dp[j][i-1]<dp[j+(1 << (i-1))][i-1])
{
dp[j][i]=dp[j][i-1];
k[j][i]=k[j][i-1];
}
else
{
dp[j][i]=dp[j+(1 << (i-1))][i-1];
k[j][i]=k[j+(1 << (i-1))][i-1];
}
}
}
ans=0;
find(1,n);
printf("Case %d: %d\n",TT-T,ans);
}
}
法4
方法解释
首先建一棵笛卡尔树,利用其性质统计求解即可,复杂度为O(n)。
代码
#include<bits/stdc++.h>
#define N 50010
using namespace std;
struct Tree
{
int lef,rig;
}tree[N];
int T,TT,num[N],fa[N],ans,n;
void find(int now,int l,int r)
{
if(now==-1) return;
ans=max(num[now]*(r-l+1),ans);
find(tree[now].lef,l,now-1);
find(tree[now].rig,now+1,r);
}
int main()
{
int i,j,now,k,gen;
cin>>T;
TT=T;
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
{
tree[i].lef=tree[i].rig=-1;
scanf("%d",&num[i]);
}
now=1;
gen=1;
fa[1]=-1;
for(i=2;i<=n;i++)
{
k=now;
while(num[i]<num[k]&&k!=-1)
{
k=fa[k];
}
if(k==-1)
{
tree[i].lef=gen;
fa[gen]=i;
fa[i]=-1;
now=gen=i;
}
else
{
tree[i].lef=tree[k].rig;
tree[k].rig=i;
now=i;
fa[i]=k;
}
}
ans=0;
find(gen,1,n);
printf("Case %d: %d\n",TT-T,ans);
}
}