某 SCOI 模拟赛 T1 矩形(rectangle)【单调栈 二分答案 堆】

| 破壁人五号 低级 OIer | 成都市第三区计算机学会提醒您 条件千万条,边界第一条 边界判不对,出错两行泪 {\tiny\text{\colorbox{grey}|破壁人五号 低级 OIer\colorbox{grey}|}}\\{\small\text{成都市第三区计算机学会提醒您}}\\ \large\text{条件千万条,边界第一条}\\ \text{边界判不对,出错两行泪} |破壁人五号 低级 OIer|成都市第三区计算机学会提醒您条件千万条,边界第一条边界判不对,出错两行泪

题意

有一个宽为 n n n 的直方图,每个条形的高度为 h i h_i hi。问所有(共 n ( n − 1 ) 2 n(n-1)\over 2 2n(n1) 个)极高子矩形中第 L L L R R R 大的的面积。

题解

下文可能会直接用一个区间 [ l , r ] [l,r] [l,r] 代表左、右延伸到 [ l , r ] [l,r] [l,r] 的极高矩形。

首先单调栈(或者别的什么办法)求出 i i i 两侧最近的、高度不及 i i i 的条形的位置,记为 L i , R i L_i,R_i Li,Ri注意处理高度相等的情况,最方便的办法是一边取小于、一边取小于等于,即维护求 L i , R i L_i,R_i Li,Ri 用的单调栈时一个取等号、一个不取),显然对于 [ l ′ , r ′ ] ∈ ( L i , R i )  且  [ l ′ , r ′ ] ∩ i ≠ ∅ [l',r']\in(L_i,R_i) \text{ 且 }[l',r']\cap i\neq \varnothing [l,r](Li,Ri)  [l,r]i=,矩形 [ l ′ , r ′ ] [l',r'] [l,r] 的高度为 h i h_i hi。(之后对于 h h h 大小关系的判断都基于 L i , R i L_i,R_i Li,Ri,可以假装每个条形的高度不同。)

首先二分第 L L L 大的矩形会有多大,check 时统计面积小于 m i d mid mid 的矩形的数量,即枚举 i i i,考虑高度为 h i h_i hi(横跨过 i i i 且在 ( L i , R i ) (L_i,R_i) (Li,Ri) 内)的矩形中有多少宽度小于 m i d h i mid\over h_i himid 的,这个分类讨论一下可以 O ( 1 ) O(1) O(1) 算;因此每次 check 是 O ( n ) O(n) O(n) 的。

从面积小于第 L L L 大矩形的矩形数量开始枚举 r k rk rk 直到 R R R。接着维护一个堆,堆中每个元素对应一个条形 i i i,把元素 i i i 按照【高度为 h i h_i hi 且排名在 r k rk rk 以上的最小矩形】(可以维护这个矩形的 l , r l,r l,r)的面积排序。每次取出堆顶,把堆顶对应的面积作为下次的答案,并尝试把 i i i 对应的矩形加到下一个,如果能加到下一个,则把 i i i 及其对应的面积 push 回堆。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lli long long
#define pii pair<lli,int>
#define fi first
#define se second
#define mp make_pair
lli getint(){
	lli ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	}
	return ans*f;
}
const int N=3e5+10;
int a[N];
lli n,l,r;

priority_queue<pii >q;
int le[N],ri[N];//the position of the nearest number that is smaller than it
int ll[N],rr[N];//the recent retangle of which the lowest part is i
void init_le(){
	stack<int>sta;
	sta.push(0);
	for(int i=1;i<=n;i++){
		while(a[sta.top()]>a[i])sta.pop();
		le[i]=sta.top();
		sta.push(i);
	}
}
void init_ri(){
	stack<int>sta;
	sta.push(n+1);
	for(int i=n;i;i--){
		while(a[sta.top()]>=a[i])sta.pop();
		ri[i]=sta.top();
		sta.push(i);
	}
}
lli calc(int i,lli len){
	if(len>=ri[i]-le[i]-1){
		ll[i]=le[i]+1;
		rr[i]=ri[i]-1;
		return (i-le[i])*1ll*(ri[i]-i);
	}
	lli p=i-le[i],q=ri[i]-i;
	lli ans=len*(len+1ll)/2;
	if(len-p>=0)ans-=(len-p)*(len-p+1ll)/2;
	if(len-q>=0)ans-=(len-q)*(len-q+1ll)/2;
	return ans;
}
lli check(lli mid){
	lli ans=0;
	for(int i=1;i<=n;i++){
		lli len=(mid-1)/a[i];
		ans+=calc(i,len);
		len=min(len,(long long)ri[i]-le[i]-1);
		if(ri[i]-len+1<=i)ll[i]=ri[i]-len,rr[i]=ri[i]-1;
		else ll[i]=i,rr[i]=i+len-1;
	}
	return ans;
}
lli nxt(int i){
	if(rr[i]==ri[i]-1||ll[i]==i)return a[i]*(rr[i]-ll[i]+2ll);
	else a[i]*(rr[i]-ll[i]+1ll);
}
bool pop(int i){
	if(ll[i]==le[i]+1&&rr[i]==ri[i]-1)return 0;
	if(rr[i]==ri[i]-1||ll[i]==i){
		if(le[i]+rr[i]-ll[i]+2<i) ll[i]=i-rr[i]+ll[i]-1,rr[i]=i;
		else rr[i]=le[i]+rr[i]-ll[i]+2,ll[i]=le[i]+1;
	}
	else ll[i]++,rr[i]++;
	return 1;
}

signed main(){
	freopen("rectangle.in","r",stdin);
	freopen("rectangle.out","w",stdout);
	n=getint();
	bool is_subtask_1=1,is_subtask_i=1; 
	for(int i=1;i<=n;i++){
		a[i]=getint();
		if(a[i]!=1)is_subtask_1=0;
		if(a[i]!=i)is_subtask_i=0;
	}
	l=getint(),r=getint();
	init_le();
	init_ri();
	for(int i=1;i<=n;i++){
		ll[i]=ri[i],rr[i]=ri[i]-1;
	}
	lli ans=0;
	{
	lli p=1,q=0x7f7f7f7f7f7f7fll,mid=0;
	while(p<=q){
		mid=(p+q)>>1;
		if(check(mid)<l)ans=mid,p=mid+1;
		else q=mid-1;
	}
	assert(ans);
	}
	int qaq=check(ans);
	for(int i=1;i<=n;i++){
		if(pop(i))q.push(mp(-(rr[i]-ll[i]+1ll)*a[i],i));
	}
	for(int i=qaq;i<=r;i++){
		if(i>=l)printf("%lld ",ans);
		pii t=q.top();q.pop();
		ans=-t.fi;
		if(pop(t.se))q.push(mp(-(rr[t.se]-ll[t.se]+1ll)*a[t.se],t.se));
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值