题解:P8246 [COCI2013-2014#3] ODAŠILJAČI
QiguaiAAAA · · 题解
题目大意
在一个长度为
PS:上面这是来自官方原题面的描述,洛谷上面的题目将描述简化了。
思路
划分区域
我们会发现,
既然对于区域
对于左侧的
如果左侧的
同理,对于右侧的
那么,我们就可以得到对于某个区域
因此,接下来的重点就是如何计算出每个区域的
如何处理区域
遍历区域
我们已经知道,对于某个区域
因此,我们可以同时从城市左端和右端分别向两边遍历建筑,构成左侧的建筑序列和右侧的建筑序列。对于每个序列,每次添加进去一个新建筑,就相应更新该序列右侧或左侧的第一个区域的
具体怎么添加一个建筑呢?我们以从左到右遍历为例。
添加一个建筑
- 添加建筑
B_i - 计算
B_i 右侧区域A_i 的L_i 。也就是:
building building_list[top_ref] = new_building;
top_ref++;
update_next_region(new_building);
因此难点在于如何计算
计算
要计算
如何计算
cover_begin_loc = max(get_cover_begin_location(building_list[i],building_list[j]),cover_begin_loc)
计算。至于函数 get_cover_begin_location(building transmitter,building barrier) 要如何实现,这就是个简单的几何问题:
但是这样子时间复杂度很大。遍历每个建筑总共
那么该怎么做?我们会发现每次添加一个新的建筑,之前的建筑都会重复计算一遍信号覆盖起始点 cover_begin_location[] 记录当前已添加建筑的信号覆盖起始点
同时,由于已经记录了每个信号发射器的信号覆盖起始点
于是我们就可以实现 update_next_region(new_building) 了:
void update_next_region(building now_building){
double best_cover_begin_loc = regions[now_building.id].to;
for(int i=stack_top_ref-1;i>=0;i--){
double cover_begin_loc = building_list[i].location+get_cover_begin_location(building_list[i],now_building);
if(cover_begin_loc>cover_begin_location[i]) cover_begin_location[i] = cover_begin_loc;
else cover_begin_loc = cover_begin_location[i];
if(cover_begin_loc<regions[now_building.id].from || cover_begin_loc>regions[now_building.id].to){
continue;
}
if(cover_begin_loc<best_cover_begin_loc)
best_cover_begin_loc = cover_begin_loc;
}
regions[now_building.id].cover_begin_l = best_cover_begin_loc;
}
以及 add(building new_building):
void add(building new_building){
if(new_building.has_transmitter){
building_list[stack_top_ref] = new_building;
cover_begin_location[stack_top_ref] = new_building.location;
stack_top_ref++;
}
update_next_region(new_building);
}
于是就可以得出一种
进一步优化
高度优化
遍历区域的
我们容易发现,如果添加进来的新建筑
如果每次添加新的建筑都删除一遍之前比它低的建筑,那么这个序列中所有信号发射器的高度必然是单调递减的。因此我们只需要将最新添加的建筑 pop() 掉,直到高度大于
while(!empty()){
if(top().height<= new_building.height) pop();
else break;
}
最优信号发射器优化
我们还会发现,如果出现下面的情况,其中蓝色建筑是新遍历到的建筑
也就是出现建筑 pop() 掉,只保留第二个建筑
当然,如果 pop() 掉
const void update_ahead_buildings(building new_building){
if(!empty()) cover_begin_location[top().id] = get_cover_begin_location(top(),new_building);
if(stack_top_ref >1) cover_begin_location[second().id] = get_cover_begin_location(second(),new_building);
}
const void pop_invalid(building new_building){
update_ahead_buildings(new_building);
while(stack_top_ref>1){
if(!is_valid(cover_begin_location[second().id],cover_begin_location[top().id])){
pop();
update_ahead_buildings(new_building);
}else break;
}
}
其中,函数 is_valid() 在从左到右遍历过程中是第二个参数比第一个参数大为 true ,在从右到左遍历过程中则反之。
这样子处理后,我们能保证在接下来计算区域的左信号覆盖起始点
void update_next_region(building now_building) override{
regions[now_building.id].cover_begin_l = regions[now_building.id].to;
if(!empty()){
double cover_begin_loc = cover_begin_location[top().id];
if(regions[now_building.id].is_in(cover_begin_loc))
regions[now_building.id].cover_begin_l = cover_begin_loc;
}
}
于是,我们成功地将
最终AC代码
见https://www.luogu.com.cn/paste/nabrsg0j
其他的一些东西
你也可以在我的个人博客上看到这篇题解(对比后你会发现洛谷这篇题解已经做了比较大幅的修改):https://blog.qiguaideaaaa.top/做题笔记:p8246-odasiljaci/。
另外这是我第一次在洛谷上发题解,有点紧张,如有疏漏还请见谅。