[洛谷] P1502 窗口的星星(扫描线)

传送门: 窗口的星星

思路:
网上多数题解思路都是将点(星星)转化为矩阵处理,这里讲一下我的思路。

首先考虑简单化的问题,如果窗口只有宽度而没有高度限制,我们可以将同一 x 坐标的点权加和,转化为一维问题,即给定一个一维数组 a[],求长度为 w 的连续子数组最大和为多少。

解决这个问题的方法有许多,最容易想到的便是双指针,当然也有其它的方法。考虑数组中每个元素的贡献范围,每个元素 a[pos] 贡献范围为 [pos, pos+w-1],即如果窗口的右边界落在这个范围内,元素 a[pos] 就会被计入窗口的区间和。

于是引出另外两种的求法:

  1. 使用优先队列维护元素的贡献范围的起始和终止位置以及权值,起始和终止位置分别对应正权和负权(差分思想),每次取出队列中起止位置最靠前的元素,累加进权值和 sum 中,sum 即为窗口右边界停在此位置时的区间和

  2. 使用线段树维护区间最大值,枚举 a[] 中所有元素对各元素对应的贡献范围进行区间加操作,全局的区间最大值便是答案,窗口的右边界为区间最大值所在位置

而原问题是这个问题的升级版,原问题的窗口是二维的,因此需要同时考虑 x、y 两个方向,可以通过将上述两种方法结合使用来解决原问题。用线段树来维护每个点在 x 方向上的区间贡献,用优先队列维护每个点在 y 方向上的贡献,按 y 大小从小到大枚举每个起始点,每个点 (x, y) 在 y 处开始对区间 [x, x+w-1] 产生贡献 val,在 y+h 处开始产生 -val 的贡献(相当于取消贡献)。

绕了一圈,最终按这个思路写出的代码与其它题解代码是一样的 =_=,那就顺便讲一下一般的解题思路。我们可以直接考虑每个点在平面上的贡献范围,每个点的贡献范围为 (x, y) 到 (x+w, y+h) 的矩形区域,即当二维窗口的右上端点落在这个区间内时,该点就会对窗口的区间和产生贡献。

求出每个点产生贡献的矩形区域,用扫描线求出平面最大值即可。

代码实现时,需注意边框上的点不能取,对于同一 y 位置处的点应同时处理,不能边处理边更新(数据水了,没同时处理居然也能过)。

最后附一组数据,输出结果自行研究:

3
9 1 1
1 1 4
1 2 9
1 3 1
2 1 8
2 2 2
2 3 7
3 1 3
3 2 6
3 3 5

1 1 1
1 1 1

4 4 4
1 1 1
1 3 100
1 5 1000
1 7 10

题解代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef tuple<int, int, int> tup;
#define fsio ios::sync_with_stdio(false)
#define ls (t<<1)
#define rs (t<<1|1)
const int inf = 0x3f3f3f3f;
const int maxn = 4e6+10;

int s[maxn], rk[maxn];
ll seg[maxn], tag[maxn];

struct P
{
    int y, x1, x2, l;
    bool operator < (const P &b) const
    {
        if (y == b.y) return l > b.l;
        return y < b.y;
    }
}eg[maxn];

void pushup(int t)
{
    seg[t] = max(seg[ls], seg[rs]);
}

void pushdown(int t, int lt, int rt)
{
    seg[ls] += tag[t];
    seg[rs] += tag[t];
    tag[ls] += tag[t];
    tag[rs] += tag[t];
    tag[t] = 0;
}

void build(int t, int tl, int tr)
{
    seg[t] = tag[t] = 0;
    if (tl == tr) return;
    int mid = (tl+tr)>>1;
    build(ls, tl, mid);
    build(rs, mid+1, tr);
}

void update(int t, int tl, int tr, int l, int r, int val)
{
    if (tr < l || tl > r) return;
    if (l <= tl && tr <= r)
    {
        seg[t] += val;
        tag[t] += val;
        return;
    }
    int mid = (tl+tr)>>1;
    pushdown(t, tl, tr);
    update(ls, tl, mid, l, r, val);
    update(rs, mid+1, tr, l, r, val);
    pushup(t);
}

int main()
{
    fsio; //fast_IO 关闭同步流
    int T;
    cin>>T;
    while(T--)
    {
        int n, w, h;
        cin>>n>>w>>h;
        for (int i = 0; i < n; i++)
        {
            int x, y, l;
            cin>>x>>y>>l;
            rk[i] = x; //保存所有x坐标,准备离散化
            rk[n+i] = x+w-1;
            eg[i] = {y, x, x+w-1, l}; //存边
            eg[n+i] = {y+h-1, x, x+w-1, -l};
        }
        sort(rk, rk+2*n);
        sort(eg, eg+2*n);
        int cnt = unique(rk, rk+2*n) - rk; //去重
        for (int i = 0; i < 2*n; i++)
        {
            eg[i].x1 = lower_bound(rk, rk+cnt, eg[i].x1) - rk; //离散化
            eg[i].x2 = lower_bound(rk, rk+cnt, eg[i].x2) - rk;
        }
        build(1, 0, cnt-1); //建树
        ll ans = 0;
        for (int i = 0; i < 2*n; i++)
        {
            auto [y, x1, x2, l] = eg[i]; //取出当前枚举的边
            update(1, 0, cnt-1, x1, x2, l); //更新该边对应的区间
            ans = max(ans, seg[1]); //更新结果
        }
        cout<<ans<<endl;
    }
    return 0;
}
posted @ 2022-07-17 17:10  Pannta  阅读(92)  评论(0编辑  收藏  举报