题解 洛谷P4198/BZOJ2957【楼房重建】

每个楼房,还有修改操作。简单的想到用线段树来维护信息。

显然线段树只需要维护y/x即可,对于每一个楼房,能看见的条件就是前面楼房的y/x的严格小于当前楼房的y/x。

线段树的区间修改不再赘述。

那么怎么维护可以看到的楼房数呢?

考虑在线段树的每一个节点上用一个变量sum来表示从这个节点的左端点向右端点看时能看到多少楼房。

假设现在有一区间:1,5,8,0,7,9。维护这个区间信息的节点编号为x

x<<1维护的区间是1,5,8,从18看可以看到三个楼房,故x<<1的sum的值为3

x<<1|1维护的区间是0,7,9,从09看也可以看到三个楼房,故x<<1|1的sum的值也为3

难道x的sum值也为3+3=6?错!

这显然是不能加在一起的,x的sum的值为4。

为什么会错呢?应为右儿子x<<1|1的观察视角不是从1开始的,是从0开始的。

显然0被挡住了。

显然,为什么这道题是紫题,主要难在的怎么上传信息。(不然就是黄牌了)

首先考虑每个线段树节点在维护一个mxmx代表这个节点所代表的区间中所有的楼房中最高的高度。

上传时,由于两个儿子的sum值是已经处理好的,而左儿子的视角是跟x的视角一样的(对于上面的例子来说都是1),可以直接上传,即sum(x)+=sum(x<<1)

对于右儿子,考虑递归处理右儿子的区间,递归时带一个Mx变量表示x的左儿子的最高的楼房(右儿子再左儿子后面,原因下面解释),递归的返回值是当前处理区间可以被看到的楼房数(从x的视角看)

对于现在处理的每一个区间:

  • 如果这个区间的最高的楼房的高度都<=Mx(全部被挡住了),立即return 0
  • 如果这个区间最左边的楼房高度>Mx(可以看到),那么return 当前区间维护的sum值。
  • 如果这个区间只代表一个楼房,直接判断其能否被看到即可。
  • 对于剩下的情况,递归儿子,分两种情况讨论:
    • 如果左儿子的最高的楼房小于Mx(不能对答案有任何贡献),直接递归右儿子。
    • 否则,递归左儿子,右儿子能看到的楼房个数为当前区间sum-左儿子sum即可,(应为下面已经更新好了,只需要直接调用就好了。)

Code(文中的sum用l代替):

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define lson x<<1
#define rson x<<1|1
using namespace std;
const int N=1e5+2;
struct Node {
    double mx;
    int l;
    #define m(x) tree[x].mx
    #define l(x) tree[x].l
} tree[N<<2];
int n,m;
double val[N];
inline void pushup_max(int x) {
    m(x)=max(m(x<<1),m(x<<1|1));
}
inline int pushup_sum(double Mx,int x,int l,int r) {
    if(m(x)<Mx)return 0;
    if(val[l]>Mx)return l(x);
    if(l==r)return val[l]>Mx;
    int mid=(l+r)>>1;
    if(m(lson)<=Mx)return pushup_sum(Mx,rson,mid+1,r);
    else return pushup_sum(Mx,lson,l,mid)+l(x)-l(lson);
}
inline void change(int x,int l,int r,int pos,int value) {
    if(l==r&&l==pos) {
        m(x)=(double)value/pos;
        l(x)=1;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)change(lson,l,mid,pos,value);
    else if(pos>mid)change(rson,mid+1,r,pos,value);
    pushup_max(x);
    l(x)=l(lson)+pushup_sum(m(lson),rson,mid+1,r);
}
int main() {
    scanf("%d%d",&n,&m);
    for(register int x,y,i=1; i<=m; ++i) {
        scanf("%d%d",&x,&y);
        val[x]=(double)y/x;
        change(1,1,n,x,y);
        printf("%d\n",l(1));
    }
    return 0;
}

转载于:https://www.cnblogs.com/K-Qiuly/p/10263195.html

### 关于 P1036 的 Java 解法 对于 P1036 题目,其核心在于模拟操作以及字符串处理。以下是基于题目描述的分析与解决方案。 #### 问题解析 该题的核心目标是对输入数据进行特定规则下的转换或计算。通常情况下,这类问题可以通过逐字符遍历或者利用数组来完成逻辑上的判断和更新[^3]。由于具体题目细节未提供,这里假设问题是关于某种模式匹配或简单算法的应用场景。 #### 实现方法 采用循环结构配合条件语句可以有效解决此类问题。下面是个可能的实现方式: ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); // 输入整数n表示测试组数 while (n-- > 0) { // 处理每组测试数据 String s = scanner.next(); // 初始化变量用于存储结果或其他中间状态 StringBuilder result = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // 根据题目需求执行相应逻辑 if (c >= 'A' && c <= 'Z') { result.append((char)(c + 32)); // 转换大小写作为例子 } else { result.append(c); // 不变的情况 } } System.out.println(result.toString()); } scanner.close(); } } ``` 上述代码展示了如何通过读取多组输入并逐处理的方式解决问题。此模板适用于许多涉及字符串变换的操作类题目[^4]。 #### 注意事项 当使用Java提交时需注意效率优化,尤其是面对大数据量的情况下。例如,在某些极端条件下,StringBuilder相比String能带来更优性能表现因为前者减少了不必要的对象创建开销[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值