矩形计数

本文介绍了如何在给定的大矩形内,不包含标记点的子矩形计数问题。通过离散化处理,利用等差数列计算内部矩形,结合扫描线算法处理边界情况,使用栈维护合法点的数量,避免重复计算。最后给出问题的解决方案并讨论了关键的算法思路。

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

题目大意:在一个以(0,0)为左下角,(w,h)为右上角的矩形内,有n个标记。统计有多少个小矩形,满足端点都在大矩形内的整点上,且平行于x轴和y轴,且这些矩形不包含任何一个被标记的点(包括边界上和端点)。这些矩形可以是退化的矩形(即面积为0)。答案mod 1 000 000 007。w,h <= 10^9可怜,n <= 2000。


首先将所有的点进行离散化,那么对于每一个枚举出来的矩形(图中加粗矩形),如图,统计出有多少个小矩形的右上角在该矩形内。

为了避免重复,该矩形的范围是[x0, x1), [y0, y1)。我们的统计分成4个部分。

1.小矩形在该矩形内,这个可以用等差数列一类的东东算出来(要知道,该矩形内是没有标记点的。为什么疑问,因为我们是离散化的);

2.小矩形左边的那一条边在该矩形左边(就是图中加粗矩形左边的灰色矩形), 右边的一条边在该矩形内。

3.小矩形下面的那一条边在该矩形下面(图中加粗矩形下面的灰侧矩形),上面的一条边也在该矩形内。

4.小矩形的左下角在合法区域内,右上角在该矩形内。所谓的合法区域,比如图中的橙色点,只要在橙色矩形内没有标记,那就是合法的点,所以橙色点是合法的,蓝色点是不合法的。这个合法点的个数统计,用一个栈来维护,因为这些点构成的形状类似楼梯的样子(图中有)微笑

统计的时候,用扫描线对x进行扫描,对于每一个x,再用一条扫描线对y进行扫描,这时候就用栈把信息传上去。

详细看代码:大笑

#include <cstdio>
#include <iostream>
using namespace std;

typedef long long ll;

//....这个读入会快些,但这题没什么必要 
inline int Scan() {
    int res = 0;
    char ch;
    
    do {
        ch = getchar();
    }while (ch < '0' || ch > '9');
    
    while (ch >= '0' && ch <= '9') {
        res = res * 10 + (int) ch - '0';
        ch = getchar();
    }
    return res;
}

#define maxn 2013

int n;
ll w, h;

#define x first
#define y second
// 坐标 
typedef pair<ll, ll> Data;

//   标记的坐标,     分别按x,y排序,(指针类型) 
Data dat[maxn], *rx[maxn], *ry[maxn];

//按x从小到大排序 
inline bool cmpx(Data *i, Data *j) {
    return i -> x < j -> x || (i -> x == j -> x && i -> y < j -> y);
}

//按y从小到大排序 
inline bool cmpy(Data *i, Data *j) {
    return i -> y < j -> y || (i -> y == j -> y && i -> x < j -> x);
}

void Init() {
    w = (ll) Scan(), h = (ll) Scan(), n = Scan();
    
    for (int i = 0; i < n; i ++) {
        dat[i].x = (ll) Scan(), dat[i].y = (ll) Scan();
        rx[i] = ry[i] = &dat[i];
    }
    
    dat[n].x = -1, dat[n].y = -1;
    rx[n] = ry[n ++] = &dat[n];     //为了处理方便,多加个点 
    
    sort(rx, rx + n, cmpx);
    sort(ry, ry + n, cmpy);
}

// 栈 
Data *list[maxn];
// 统计点数 
ll area[maxn];

const ll mod = 1000000007;

// 1 + 2 + 3 + ... + a 的和 
inline ll C(ll a) {
    return (((1 + a) * a) >> 1) % mod;
}

inline ll add(ll a, ll b) {
    return (a + b) % mod;
}

inline ll mul(ll a, ll b) {
    return (a * b) % mod;
}

void Solve() {
    ll res = 0LL;
    
    //再多加个点, 方便统计 
    rx[n] = ry[n] = &dat[n];
    dat[n].x = w + 1, dat[n].y = h + 1;
    
    //初始化栈底 
    list[0] = new Data;
    list[0] -> x = list[0] -> y = -1; 
    area[0] = 0;
    
    for (int i = 0; i < n; i ++)
        // 对于x的扫描线 
        if (rx[i] -> x != rx[i+1] -> x) {
            int x0 = rx[i] -> x, x1 = rx[i+1] -> x;
            
            int j = 0, top = 0;
            for (; j < n; j ++) {
                // 对于y的扫描线 
                if (ry[j] -> x > x0) continue;
                // 若纵坐标相等,则取横坐标最大的
                while (ry[j] -> y == ry[j+1] -> y && ry[j+1] -> x <= x0) j ++;
                
                // 找出下一条扫描线
                int p = j + 1;
                while (p < n && (ry[j] -> y == ry[p] -> y || ry[p] -> x > x0))
                    p ++;
                
                int y0 = ry[j] -> y, y1;
                if (p == n) y1 = h + 1;
                else y1 = ry[p] -> y;
                
                
                //统计1 
                res = add(res, mul(C(x1 - x0 - 1), C(y1 - y0 - 1)));
                //统计2 
                res = add(res, mul(mul(x0 + 1, C(y1 - y0 - 1)), x1 - x0));
                //统计3 
                res = add(res, mul(mul(y0 + 1, C(x1 - x0 - 1)), y1 - y0));
                
                //把不合法的弹出 
                while (top > 0 && list[top] -> x < ry[j] -> x) top --;
                
                //加入当前的点 
                list[++ top] = ry[j];
                area[top] = add(area[top-1],
                    mul(y0 - list[top-1] -> y, x0 - list[top] -> x));
                
                //统计4 
                res = add(res, mul(area[top], 
                               mul(x1 - x0, y1 - y0)));
            }
        }
    
    cout << res << endl;
}

int main() {
    freopen("rectangles.in", "r", stdin);
    freopen("rectangles.out", "w", stdout);
    
    Init();
    
    Solve();
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值