人工湖题解

来自bzoj.org/p/Z2512(传统题)


题面

夏日那让人喘不过气的酷热将奶牛们的烦躁情绪推到了最高点。

最终,FJ决定建一个人工湖供奶牛消暑之用。

为了使湖看起来更加真实,FJ决定将湖的横截面建成N(1 <= N <= 100,000)个连续的平台高低错落的组合状,

所有的平台从左到右按1..N依次编号。

当然咯,在湖中注入水后,这些平台都将被淹没。

平台i在设计图上用它的宽度W_i(1 <= W_i <= 1,000)和高度

(你可以理解为该平台顶离FJ挖的地基的高度)H_i(1 <= H_i <= 1,000,000)来描述的。

所有平台的高度都是独一无二的。湖的边缘可以视为无限高的平台。

按FJ的设想,在坑挖好后,他会以1单位/分钟的速度往最低的那个平台上注水。

水在离开水管后立即下落,直到撞到平台顶或是更早些时候注入的水。然后,与所有常温下的水一样,它会迅速地流动、扩散。

简单起见,你可以认为这些都是在瞬间完成的。

FJ想知道,对于每一个平台,它的顶部是从哪个时刻开始,与水面的距离至少为1单位长度。

注意:数据不保证答案全部在32位整型变量的范围内。

例:

in

3
4 2      
2 7
6 4

ans

4
50
26

这道题看上去非常恐怖,但我们看懂题面后,也不难理解。

把图画出来,发现可以用单调栈求解:

从这张图不难看出,我们会在红色处灌水,要淹没某处,需在上面灌注高1个单位的水,很显然,这与宽度有关,淹没红色处要4个单位的水,需要4分钟。

那么,接下来要继续灌水,当水的高度达到5时,达到了蓝色区域的高度。接下来灌的水会流向低处的黄色区域。

在38秒时,水覆盖到蓝色区域。这时,要覆盖蓝色区域,宽度并不是2,而是12,因此,灌完蓝色区域需要50分钟。

难点在于:灌水点是不变的,还要遵循自然:要填高处,先填低处,因此模拟起来极其困难(普及组的题让我写出了NOI++的感觉QAQ!)


这个样例强度太低,看看这个更加复杂的样例:

in:

5
1 2
1 3
1 5
1 7
1 1
ans:

7
9
14
22
1
这个样例非常好,过了这个,整道题都没有问题了。

还是先画图

水从橙色区域灌入,将编号5和宽度1进栈(结构体)。

然后,准备两个变量l和r,赋值为5。

从high[r + 1]和high[l - 1]中取最小值,对r或l进行加减操作,并把对应的编号记录在变量id中(由于把边缘赋值为无穷大,无须担心越界),开始运行函数:

void water(int id)
{
	int sum = 0;
	while(!s.empty() && high[s.top().index] < high[id])
	{
		sum += s.top().width;
		ans[s.top().index] = nt + sum;
		int x = high[s.top().index];
		s.pop();
		if(!s.empty()) nt += sum * (min(high[s.top().index],high[id]) - x);
		else nt += sum * (high[id] - x);
	}
	s.push({id,sum + width[id]});
	return;
}

这个函数是核心代码,通过维护一个严格上升的单调栈,从而来完成灌水的任务。


如果栈不为空 && 栈顶元素的index所代表的high小于id所代表的high

{

把sum(宽度累加器)加上s.top().width,并把nt(现在的时间)与sum的和作为ans[s.top().index]。

开一个临时变量x = high[s.top().index)],然后弹出栈顶元素。

取栈顶元素的高于与id的高中的最小值,减去x,再乘以sum,加给nt。

一定要判定栈不为空,若栈为空,则只加上high[id] - x。

}

最后,把id和width[id]加入栈。


这个函数需要运行n次,每次参数都用high[l - 1] < high[r + 1] ? --l : ++r。

最后运行一次water(0),high[0]为无穷大,会把所有区域全部灌满,并将ans输出。


代码如下:

#include<iostream>
#include<stack>
using namespace std;
struct hu
{
	long long index,width;
};
stack <hu> s;
long long n,ans[100005],high[100005],width[100005],minn = 1145141145,minx,nt;
void water(int id)
{
	int sum = 0;
	while(!s.empty() && high[s.top().index] < high[id])
	{
		sum += s.top().width;
		ans[s.top().index] = nt + sum;
		int x = high[s.top().index];
		s.pop();
		if(!s.empty()) nt += sum * (min(high[s.top().index],high[id]) - x);
		else nt += sum * (high[id] - x);
	}
	s.push({id,sum + width[id]});
	return;
}
int main()
{
	cin >> n;
	for(int i = 1;i <= n;i++)
	{
		cin >> width[i] >> high[i];
		if(minn > high[i]) minn = high[i],minx = i;
	}
	high[0] = high[n + 1] = 1000000000;
	int l = minx,r = minx;
	s.push({minx,width[minx]});
	for(int i = 1;i <= n;i++) water(high[l - 1] < high[r + 1] ? --l : ++r);
	water(0);
	for(int i = 1;i <= n;i++) cout << ans[i] << endl;
}

案例运行结果:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值