2022牛客多校 C Grab the Seat!

这篇博客介绍了如何解决二维平面上座位视线不被挡的问题。具体是通过处理不同y值列的情况,利用射线和折线图的概念,维护最大和最小斜率来确定视线不被阻挡的座位数量。算法涉及线段树、区间查询和动态规划思想,适用于处理动态修改坐标后的查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

二维平面,屏幕是 (0, 1)–(0, m) 的线段
有 n 行 m 列座位在屏幕前面,是坐标范围 1 ≤ x ≤ n, 1 ≤ y ≤ m 的整点
有 k 个座位已经有人,求出到屏幕的视线不被任何人挡住的座位数量
q 次询问,每次修改一个人的坐标后求出答

题解
对于 y = 1 或 y = m 的列,每个人只会挡住自己正右方的人。特殊处理
一下。
而对于 y = 2, 3, . . . , m − 1,每个人挡住的区域是 (0, 1) 和 (0, m) 到这个
点的两条射线之间的区域。两条射线的方向都是 x 坐标增大,y 坐标一个增
大一个减小。最后不合法的区域就是这些区域的并集。
整个区域实际上构成一个折线图。对于每个 y,会有一个分界线 t,在这
一列上,x ∈ [1, t) 的点是满足条件的,而 x ∈ [t, n] 的点会被挡住。
折线图由 2k 个线段构成。这 2k 条线段有比较明显的性质,就是延长线
都经过 (0, 1) 或 (0, m)。可以把它们分成两类。
那么实际上可以对于延长线经过这两个点的线段分别维护一下斜率。
首先对每个 y,如果这一列有多个人,那么肯定考虑 x 最小的人,因为
小的会完全覆盖大的。
然后对于 (0, 1) 和 (0, m) 分开考虑。
以 (0, 1) 为例,所有线段都是从某个点往右上方走。
如果按 y 从小到大加入线段,不难发现斜率大的线段一旦加入,就一直
会覆盖斜率小的线段。对于某个 y,要找到最小的合法 x,那么肯定取的是
前面所有线段中斜率最大的线段。所以对 y 正着扫一遍,在扫的同时维护一
下最大的斜率,即可得出每一列只考虑经过 (0, 1) 的线段的答案。
(0, m) 同理,倒着扫一遍维护斜率最小(绝对值最大)的线段即可。每
个 y 在两种情况取 min 就是答案。

#include <algorithm>
#include <iostream>
#include <cmath>
#include <vector>
#include <stack>
#include <cstring>
#include <cstdio>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip> //保留小数setprecision
#define endl '\n'
#define PI acos(-1.0) // 3.1415
#define Buff std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
#define lowbit(x) x & -x
#define ll long long
#define ull unsigned long long
#define eps 1e-10
#define int long long
//#pragma GCC optimize(2)
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 7, M = 5e5 + 7;
const int mod = 1e9 + 7;

int n, m, k, q;
int h[N], X[N], Y[N], MIN[N];

struct node
{
    int a, b;
    bool operator<(const node &x) const //按照斜率从大到小排序,斜率是b/a,不等式转移就是下面的式子,这个式子从小到大对应斜率从大到小。
    {
        return a * x.b < x.a * b;
    }
};

inline void work()
{

    fill(h + 1, h + m + 1, n);
    fill(MIN + 1, MIN + m + 1, n + 1);

    for (int i = 1; i <= k; i++) //求每行有人的座位求最小的x;
        MIN[Y[i]] = min(MIN[Y[i]], X[i]);

    node L = {INF, 1LL};

    for (int i = 2; i <= m; i++)
    {
        if (L.a != INF)
        {
            int x = L.a, y = L.b;
            //(x * (i - 1)) / y是找到维护的最大斜率在这一行的交点
            //将直线的式子写出来就可以很容易理解了
            // y=(b/a)x+c,这里c=1.
            if (x * (i - 1) % y == 0) //如果交点刚好为整数(及刚好在点上),那就-1
                h[i] = min(h[i], (x * (i - 1)) / y - 1);
            else
                h[i] = min(h[i], (x * (i - 1)) / y);
        }
        L = min(L, {MIN[i], i - 1}); //求最大的斜率
    }

    node R = {INF, 1LL};

    for (int i = m - 1; i >= 1; i--)
    {
        if (R.a != INF)
        {
            int x = R.a, y = R.b;
            // h[i] = min(h[i], (x * (m - i) - 1) / y);
            if (x * (m - i) % y == 0)
                h[i] = min(h[i], (x * (m - i)) / y - 1);
            else
                h[i] = min(h[i], (x * (m - i)) / y);
        }
        R = min(R, {MIN[i], m - i});
    }

    int res = 0;
    for (int i = 1; i <= m; i++)
    {
        h[i] = min(h[i], MIN[i] - 1);
        res += h[i];
    }
    cout << res << endl;
    return;
}

inline void solve()
{
    cin >> n >> m >> k >> q;

    for (int i = 1; i <= k; i++)
    {
        cin >> X[i] >> Y[i];
    }

    while (q--)
    {
        int p, x, y;
        cin >> p >> x >> y;
        X[p] = x, Y[p] = y;
        work();
    }

    return;
}
signed main()
{
    int _ = 1;
    // cin>> _;
    while (_--)
        solve();
    return 0;
}
/*
 *
 *  ┏┓   ┏┓+ +
 * ┏┛┻━━━┛┻┓ + +
 * ┃       ┃
 * ┃   ━   ┃ ++ + + +
 *  ████━████+
 *  ◥██◤ ◥██◤ +
 * ┃   ┻   ┃
 * ┃       ┃ + +
 * ┗━┓   ┏━┛
 *   ┃   ┃ + + + +Code is far away from bug with the animal protecting
 *   ┃   ┃ +                     神兽保佑,代码无bug 
 *   ┃    ┗━━━┓
 *   ┃        ┣┓
 *    ┃        ┏┛
 *     ┗┓┓┏━┳┓┏┛ + + + +
 *    ┃┫┫ ┃┫┫
 *    ┗┻┛ ┗┻┛+ + + +
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值