【POJ 1151】Atlantis

本文介绍了一种通过离散化和扫描线算法结合线段树数据结构来解决多个矩形面积并的问题。通过实例展示了如何避免内存溢出,并讨论了算法调试过程中遇到的挑战。

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

题目描述

n(n100)n(n≤100) 个矩形的面积并。

吐槽

此题十分玄学,开始敲了一份代码,调了一晚上不过,返回 MLE

今天上午继续调试,发现网上的代码开 G++ 过不去,发现原来是卡 %lf,改成 %f 网上的代码就能过了,对拍了半天拍不出错,肉眼查出 query(int x) 那里应该是 query(double x),不然会 RE 做了很多简化之后在 入门OJ 上过了,但在 POJ 上 WA,去掉简化的内容就在 POJ 上过了,心累。

算法分析

离散化后使用扫描线,从左向右(也可以从上到下)扫描矩形与扫描线平行的边,用线段树维护目前的覆盖情况。

这个线段树不是普通的线段树,普通线段树貌似搞不了,我们发现,对于区间的增减总是成对出现,那么这里的线段树就是仅仅用来将线段拆分成 log2nlog2n 段以降低时间复杂度的,因此不必下传标记,只要记录当前段被覆盖的次数,维护当前区间内被覆盖的长度即可。

代码实现

#include <cstdio>
#include <algorithm>
const int maxn=105;
struct ask {
    double t,x,l,r;
    bool operator < (const ask &rhs) const {
        return x<rhs.x;
    }
} asks[2*maxn];
double hash[2*maxn];int idx=0;
inline int query(double x) {return std::lower_bound(hash,hash+idx,x)-hash;}
namespace SGT {
    int n,cnt[4*2*maxn];double ans[4*2*maxn];
    void build(int o,int l,int r) {
        if(l==r) ans[o]=cnt[o]=0;
        else {
            int mid=(l+r)>>1;
            build(o<<1,l,mid);build(o<<1|1,mid+1,r);
            ans[o]=cnt[o]=0;
        }
    }
    inline void init(int N) {n=N;build(1,1,n);}
    int ql,qr;
    void add(int o,int l,int r) {
        if(ql<=l&&r<=qr) {
            ++cnt[o];
            ans[o]=hash[r]-hash[l-1];
        }
        else {
            int mid=(l+r)>>1;
            if(ql<=mid) add(o<<1,l,mid);
            if(mid+1<=qr) add(o<<1|1,mid+1,r);
            if(!cnt[o]) ans[o]=ans[o<<1]+ans[o<<1|1];
        }
    }
    inline void add(int l,int r) {ql=l;qr=r;add(1,1,n);}
    void clear(int o,int l,int r) {
        if(ql<=l&&r<=qr) {
            --cnt[o];
            if(!cnt[o]) ans[o]=ans[o<<1]+ans[o<<1|1];
        }
        else {
            int mid=(l+r)>>1;
            if(ql<=mid) clear(o<<1,l,mid);
            if(mid+1<=qr) clear(o<<1|1,mid+1,r);
            if(!cnt[o]) ans[o]=ans[o<<1]+ans[o<<1|1];
        }
    }
    inline void clear(int l,int r) {ql=l;qr=r;clear(1,1,n);}
    inline double query() {return ans[1];}
}
int main() {
    int n,kase=0;
    while(scanf("%d",&n)==1&&n) {
        idx=0;double x1,y1,x2,y2;
        for(int i=0;i<n;++i) {
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            asks[i]=(ask){1,x1,y1,y2};asks[n+i]=(ask){-1,x2,y1,y2};
            hash[idx++]=y1;hash[idx++]=y2;
        }
        std::sort(hash,hash+idx);idx=std::unique(hash,hash+idx)-hash;
        std::sort(asks,asks+2*n);
        SGT::init(idx);
        double ans=0,last=0;
        for(int i=0;i<2*n;++i) {
            int l=query(asks[i].l)+1,r=query(asks[i].r);
            ans+=SGT::query()*(asks[i].x-last);
            if(asks[i].t==1) SGT::add(l,r);
            else if(asks[i].t==-1) SGT::clear(l,r);
            last=asks[i].x;
        }
        if(kase) printf("\n");
        printf("Test case #%d\nTotal explored area: %.2f\n",++kase,ans);
    }
    return 0;
}
<think>首先,用户的问题是关于解决POJ3695 Rectangles的算法问题,具体是计算多个矩形的总面积。用户提到了参考引用,其中引用[1]提到了POJ3695 Rectangles,并说使用组合数学中的容斥原理解决最适合,因为是多查询且矩形不多。引用[4]提到了POJ1151 Atlantis,使用朴素算法和线段树解法,但那是另一个问题。 我的回答需要遵循系统级指令:1.行内数学表达式用$...$格式。2.独立公式用$$...$$格式并单独成段。 3. LaTeX语法正确。4.使用中文回答。 5.生成相关问题。 6.回答中引用的段落末尾自然地添加引用标识,例如[^1]。用户可见层指令:回答结构清晰,帮助用户逐步解决问题,保证真实可靠。用户的问题核心是:如何解决POJ3695 Rectangles,即计算多个矩形的总面积,特别是因为是多查询,矩形数量不多。从引用[1]:使用容斥原理解决。引用[1]说:"使用组合数学中的容斥原理解决之最适合。线段树是通法,但是除了线段树,还有其他可行的方法。" POJ3695Rectangles是一个计算几何问题,涉及计算多个矩形的并集面积。由于是多查询(multiplequeries),矩形数量不多(题目中矩形数量较小),所以容斥原理适合。 回想一下容斥原理:对于多个集合的并集大小,可以用单个集合大小减去两两交集大小加上三个交集大小,依此类推。对于矩形并集面积,类似。每个矩形是轴对齐的(axis-aligned),在POJ问题中,通常是轴对齐矩形。 问题描述:给定多个矩形,每个矩形由左下角和右上角坐标定义,计算所有矩形的总面积(即并集面积)。由于是多查询,输入可能包含多个查询集,每个查询集有多个矩形。在POJ3695,输入格式:第一行是整数n(矩形数量),然后n行每行四个整数x1 y1 x2 y2(左下角和右上角坐标),然后可能有多组查询。引用[1]提到"多次查询",所以可能有多个测试用例。容斥原理方法:对于k个矩形,并集面积S =Σarea(Ri)- Σarea(Ri∩ Rj)+ Σarea(Ri ∩Rj∩ Rk)- ...+ (-1)^{k+1}area(R1 ∩R2 ∩... ∩Rk) 由于矩形数量不多,我们可以计算所有子集的交集面积。 在代码中,我们需要计算所有非空子集的交集面积,并根据子集大小添加符号。具体步骤: 1.对于给定的一组矩形,生成所有非空子集。2.对于每个子集,计算子集中所有矩形的交集矩形。如果交集为空,则面积为0。 3.交集矩形:对于轴对齐矩形,交集是[max(x1),max(y1)]到 [min(x2),min(y2)],但需要检查是否有效(即max(x1)<min(x2)且max(y1)< min(y2))。 4.然后,根据子集大小,如果是奇数,加面积;偶数,减面积。5.最后得到并集面积。由于矩形数量n不大(在POJ3695,n可能较小,如不超过20),所以2^n子集是可行的。引用[1]说"矩形不多",所以n小。 现在,回答结构: -先解释问题:POJ3695Rectangles要求计算多个矩形的并集面积,有多个查询。 -然后介绍容斥原理方法。-给出算法步骤。-可能提供伪代码或代码示例(但用户没有指定语言,所以用中文描述)。-最后生成相关问题。引用标识:我需要引用参考内容。用户提供了引用[1]到[4],其中引用[1]直接相关。在回答中,当提到容斥原理时,可以引用[1]。例如:"如引用[1]所述,使用容斥原理解决此问题最适合。" 其他引用可能不直接相关,但引用[4]是关于POJ1151,使用线段树,但那是另一个问题,涉及扫描线算法。对于POJ3695,由于矩形不多,容斥原理更简单。 用户提到"计算多个矩形的总面积",但POJ3695是并集面积,不是总面积。总面积通常指并集,避免重复计算。是的。 现在,数学表达式:当描述容斥原理时,使用行内或独立公式。 例如,行内:$S= \sum_{i} A_i -\sum_{i<j}A_{i\capj}+ \sum_{i<j<k} A_{i\capj \cap k} -\cdots$ 独立公式:单独成段。 使用中文回答。 生成相关问题:在最后,添加相关问题。相关问题应基于主题,如计算几何、容斥原理、其他方法等。例如:1.容斥原理在其他问题中的应用? 2.如果矩形数量大,如何优化?如使用扫描线算法。 3. POJ1151 Atlantis的解法比较。 等等。 回答结构:1.**问题描述**:简要描述POJ3695 Rectangles问题。 2. **算法选择**:解释为什么容斥原理适合,引用[1]。3.**容斥原理详解**:解释公式和计算步骤。4.**伪代码或实现思路**:提供算法框架。 5. **复杂度分析**:由于n小,2^n可接受。6.**相关问题**:生成3-5个相关问题。 最后,确保LaTeX正确。</think>### POJ 3695 Rectangles 算法解法:计算多个矩形的并集面积 POJ 3695 Rectangles 是一个计算几何问题,要求计算多个轴对齐矩形(axis-aligned rectangles)的总面积(即并集面积,避免重复计算)。输入包括多个查询集,每个查询集包含若干矩形(矩形数量通常较小,如不超过20),每个矩形由左下角坐标 $(x_1, y_1)$ 和右上角坐标 $(x_2, y_2)$ 定义。问题核心是高效处理多查询场景,且矩形数量不多时,使用组合数学中的容斥原理(Inclusion-Exclusion Principle)是最佳方法,如引用[1]所述[^1]。以下我将逐步解释算法原理、实现步骤和复杂度分析,确保回答清晰可靠。 #### 问题背景与算法选择 - **问题描述**:给定 $n$ 个矩形,计算它们的并集面积(即所有矩形覆盖的总区域,重叠部分只算一次)。输入可能包含多个测试用例(多查询),每个用例的矩形数 $n$ 较小。 - **为什么选择容斥原理**: 当矩形数量 $n$ 较小(例如 $n \leq 20$)时,容斥原理能直接计算所有子集的交集面积,实现简单高效。相比之下,线段树扫描线算法(如引用[4]中的POJ1151解法)更适合矩形数量大的场景,但这里矩形不多,容斥原理更优[^1]。 - **容斥原理公式**: 对于 $n$ 个矩形的集合,并集面积 $S$ 可表示为: $$S = \sum_{i} A_i - \sum_{i<j} A_{i \cap j} + \sum_{i<j<k} A_{i \cap j \cap k} - \cdots + (-1)^{k+1} A_{S_{\text{all}}}$$ 其中: - $A_i$ 表示单个矩形 $i$ 的面积。 - $A_{i \cap j}$ 表示矩形 $i$ 和 $j$ 的交集面积。 - $k$ 为子集大小,符号 $(-1)^{k+1}$ 表示:奇数大小子集加面积,偶数大小子集减面积。 - 如果子集的交集为空,则面积为 $0$,无需计算。 #### 算法步骤详解 1. **计算单个矩形面积**: 对于矩形 $i$,面积 $A_i = (x_2 - x_1) \times (y_2 - y_1)$,其中 $(x_1, y_1)$ 和 $(x_2, y_2)$ 为坐标。 2. **计算子集交集矩形**: 对于每个非空子集(子集大小从 $1$ 到 $n$),计算该子集中所有矩形的交集矩形: - 交集矩形的左下角:$(\max(x_1), \max(y_1))$。 - 交集矩形的右上角:$(\min(x_2), \min(y_2))$。 - 检查交集是否有效:如果 $\max(x_1) < \min(x_2)$ 且 $\max(y_1) < \min(y_2)$,则交集存在,面积 $A_{\text{intersect}} = (\min(x_2) - \max(x_1)) \times (\min(y_2) - \max(y_1))$;否则,面积为 $0$。 3. **应用容斥原理求和**: - 遍历所有非空子集(子集总数 $2^n - 1$ 个)。 - 对每个子集,根据其大小 $k$: - 如果 $k$ 为奇数($k \mod 2 = 1$),则加该子集的交集面积。 - 如果 $k$ 为偶数($k \mod 2 = 0$),则减该子集的交集面积。 - 最终和即为并集面积 $S$。 4. **处理多查询**: 由于输入可能有多个测试用例,对每个用例重复上述步骤。 #### 伪代码实现 以下是用 Python 风格的伪代码展示算法框架(实际实现时需处理输入输出): ```python def calculate_union_area(rectangles): n = len(rectangles) total_area = 0 # 遍历所有非空子集 (子集掩码从 1 到 2^n - 1) for bitmask in range(1, 1 << n): # 初始化交集矩形的边界 x1_intersect = float('-inf') y1_intersect = float('-inf') x2_intersect = float('inf') y2_intersect = float('inf') count = 0 # 子集大小 k # 检查当前子集包含的矩形 for i in range(n): if bitmask & (1 << i): # 矩形 i 在子集中 count += 1 x1, y1, x2, y2 = rectangles[i] x1_intersect = max(x1_intersect, x1) y1_intersect = max(y1_intersect, y1) x2_intersect = min(x2_intersect, x2) y2_intersect = min(y2_intersect, y2) # 检查交集是否有效 if x1_intersect < x2_intersect and y1_intersect < y2_intersect: area_intersect = (x2_intersect - x1_intersect) * (y2_intersect - y1_intersect) else: area_intersect = 0 # 根据子集大小添加或减去面积 if count % 2 == 1: total_area += area_intersect else: total_area -= area_intersect return total_area # 主程序处理多查询 while True: n = int(input().strip()) if n == 0: break rectangles = [] for _ in range(n): x1, y1, x2, y2 = map(int, input().split()) rectangles.append((x1, y1, x2, y2)) area = calculate_union_area(rectangles) print(area) ``` #### 复杂度分析 - **时间复杂度**:$O(2^n \times n)$,其中 $n$ 是矩形数量。因为需遍历 $2^n$ 个子集,每个子集处理 $n$ 个矩形。当 $n \leq 20$ 时,$2^{20} \approx 10^6$,可接受。 - **空间复杂度**:$O(n)$,仅需存储矩形坐标。 - **优势与局限**:容斥原理实现简单,适合小 $n$;但如果 $n$ 较大(如 $n > 20$),应改用扫描线算法(时间复杂度 $O(n \log n)$),如引用[4]中的POJ1151解法[^4]。 #### 相关拓展 - **为什么容斥原理有效**:它通过加减交集来消除重叠区域的重复计算,本质是集合论的公式在几何中的应用。 - **边界处理**:坐标可能为整数或浮点数,需确保比较时处理精度问题(POJ 3695 中坐标通常为整数)。 - **与其他问题比较**:在POJ1151 Atlantis(引用[4])中,矩形数量大,使用扫描线算法更高效[^4]。但对于POJ 3695,容斥原理是首选[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值