team2012-E1-mysol-0011

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

{{{
/* 解题报告 //

给出 n <= 1000 个矩形的绘制或擦除操作,再给出 m <= 100 段的折线段
求线段上被绘制部分的长度

这题我在比赛时一直考虑如何做矩形切割,得到一个个互不相交的小矩形
然后再对折线段的覆盖面积进行计算

实际上,这么做是很困难的,因为最坏情况下矩形有 O(n ^ 2) 个
乘上线段的数量再乘上 case 数后很容易超时
且 O(n ^ 2 log n) 的矩形切割本身也很慢了,而且相当不好写
更何况边界的处理是非常难的一件事

如果换一种想法,直接对每条线段处理在其之上的所有矩形
就可以将矩形添加与删除,变成线段的添加与删除

于是依次处理,对于每条线段 i,求出它与每个矩形 j 的相交区间 Cij
将每个区间化成两个事件点 (pos_left, j_start) 和 (pos_right, j_end)
放到 vector 中,等到相交的部分处理完后,对整个 vector 排一下序

然后对 vector 遍历,在处理每个事件之前,
先计算当前区间(上个事件点的位置到这个事件点的位置)是否被覆盖
可以用一个 set<int> 去维护覆盖当前区间的所有矩形的编号
计算时,取出编号最大的矩形,如果是绘制操作,则 ans += 当前区间长度
计算完当前区间后,再处理事件:
1. 如果类型是 j_start,则将 j 插入到 set<int> 内
2. 如果类型是 j_end,则从 set<int> 中删除 j

关于计算矩形与线段相交具体是怎么做的,zYc 学长那边写得比较详细
大家可以去看看他的解题报告

// By 猛犸也钻地 @ 2012.07.21 */
}}}

{{{
#include <cstdio>
#include <vector>
#include <set>
#include <cmath>
#include <algorithm>
using namespace std;

typedef pair<double,double> PDD;
typedef pair<double,int> PDI;

int tp[1000],ax[1000],ay[1000],bx[1000],by[1000];

inline double sqr(double x){
    return x*x;
}

PDD cross(int i, int sx, int sy, int tx, int ty){
    if(sx>tx) swap(sx,tx),swap(sy,ty);
    double ux=max(sx,ax[i])-sx,vx=min(tx,bx[i])-sx;
    double lx=abs(tx-sx),ly=abs(ty-sy),uy=0,vy=0;
    if(sy<ty) uy=max(sy,ay[i])-sy,vy=min(ty,by[i])-sy;
    if(sy>ty) uy=sy-min(sy,by[i]),vy=sy-max(ty,ay[i]);
    if(sx==tx && sx>=ax[i] && sx<=bx[i]) return PDD(uy/ly,vy/ly);
    if(sy==ty && sy>=ay[i] && sy<=by[i]) return PDD(ux/lx,vx/lx);
    if(sx!=tx && sy!=ty) return PDD(max(ux/lx,uy/ly),min(vx/lx,vy/ly));
    return PDD(-1,-2);
}

int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)==2){
        for(int i=0;i<n;i++)
            scanf("%d%d%d%d%d",tp+i,ax+i,ay+i,bx+i,by+i);
        int sx,sy,tx,ty;
        scanf("%d%d",&sx,&sy);
        double ans=0;
        for(int i=1;i<m;i++){
            scanf("%d%d",&tx,&ty);
            vector<PDI> u;
            for(int j=0;j<n;j++){
                PDD c=cross(j,sx,sy,tx,ty);
                if(c.first>=c.second) continue;
                u.push_back(PDI(c.first,~j));
                u.push_back(PDI(c.second,j));
            }
            sort(u.begin(),u.end());
            set<int> o;
            double p=0,len=sqrt(sqr(tx-sx)+sqr(ty-sy));
            for(size_t j=0;j<u.size();p=u[j++].first){
                if(o.size() && tp[*o.rbegin()]) ans+=(u[j].first-p)*len;
                if(u[j].second<0) o.insert(~u[j].second); else o.erase(u[j].second);
            }
            sx=tx,sy=ty;
        }
        printf("%.2f\n",ans);
    }
}
}}}
/* 解题报告 //
给出 n <= 1000 个矩形的绘制或擦除操作,再给出 m <= 100 段的折线段
求线段上被绘制部分的长度
这题我在比赛时一直考虑如何做矩形切割,得到一个个互不相交的小矩形
然后再对折线段的覆盖面积进行计算
实际上,这么做是很困难的,因为最坏情况下矩形有 O(n ^ 2) 个
乘上线段的数量再乘上 case 数后很容易超时
且 O(n ^ 2 log n) 的矩形切割本身也很慢了,而且相当不好写
更何况边界的处理是非常难的一件事
如果换一种想法,直接对每条线段处理在其之上的所有矩形
就可以将矩形添加与删除,变成线段的添加与删除
于是依次处理,对于每条线段 i,求出它与每个矩形 j 的相交区间 Cij
将每个区间化成两个事件点 (pos_left, j_start) 和 (pos_right, j_end)
放到 vector 中,等到相交的部分处理完后,对整个 vector 排一下序
然后对 vector 遍历,在处理每个事件之前,
先计算当前区间(上个事件点的位置到这个事件点的位置)是否被覆盖
可以用一个 set<int> 去维护覆盖当前区间的所有矩形的编号
计算时,取出编号最大的矩形,如果是绘制操作,则 ans += 当前区间长度
计算完当前区间后,再处理事件:
1. 如果类型是 j_start,则将 j 插入到 set<int> 内
2. 如果类型是 j_end,则从 set<int> 中删除 j
关于计算矩形与线段相交具体是怎么做的,zYc 学长那边写得比较详细
大家可以去看看他的解题报告
// By 猛犸也钻地 @ 2012.07.21 */
#include <cstdio>
#include <vector>
#include <set>
#include <cmath>
#include <algorithm>
using namespace std;
typedef pair<double,double> PDD;
typedef pair<double,int> PDI;
int tp[1000],ax[1000],ay[1000],bx[1000],by[1000];
inline double sqr(double x){
    return x*x;
}
PDD cross(int i, int sx, int sy, int tx, int ty){
    if(sx>tx) swap(sx,tx),swap(sy,ty);
    double ux=max(sx,ax[i])-sx,vx=min(tx,bx[i])-sx;
    double lx=abs(tx-sx),ly=abs(ty-sy),uy=0,vy=0;
    if(sy<ty) uy=max(sy,ay[i])-sy,vy=min(ty,by[i])-sy;
    if(sy>ty) uy=sy-min(sy,by[i]),vy=sy-max(ty,ay[i]);
    if(sx==tx && sx>=ax[i] && sx<=bx[i]) return PDD(uy/ly,vy/ly);
    if(sy==ty && sy>=ay[i] && sy<=by[i]) return PDD(ux/lx,vx/lx);
    if(sx!=tx && sy!=ty) return PDD(max(ux/lx,uy/ly),min(vx/lx,vy/ly));
    return PDD(-1,-2);
}
int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)==2){
        for(int i=0;i<n;i++)
            scanf("%d%d%d%d%d",tp+i,ax+i,ay+i,bx+i,by+i);
        int sx,sy,tx,ty;
        scanf("%d%d",&sx,&sy);
        double ans=0;
        for(int i=1;i<m;i++){
            scanf("%d%d",&tx,&ty);
            vector<PDI> u;
            for(int j=0;j<n;j++){
                PDD c=cross(j,sx,sy,tx,ty);
                if(c.first>=c.second) continue;
                u.push_back(PDI(c.first,~j));
                u.push_back(PDI(c.second,j));
            }
            sort(u.begin(),u.end());
            set<int> o;
            double p=0,len=sqrt(sqr(tx-sx)+sqr(ty-sy));
            for(size_t j=0;j<u.size();p=u[j++].first){
                if(o.size() && tp[*o.rbegin()]) ans+=(u[j].first-p)*len;
                if(u[j].second<0) o.insert(~u[j].second); else o.erase(u[j].second);
            }
            sx=tx,sy=ty;
        }
        printf("%.2f\n",ans);
    }
}