[研究ノート]の走査線

走査線についてI.

  そして、それは、境界や面積に基づいて、アルゴリズムを求めています。

  走査線が存在しないラインであることに注意してください。

  パターンの底から(左から右へ、または掃引)上記走査線ダウンがあると、パターンは、走査線を分析することによって得られた線は、望ましい結果を得ることができる切断されます。

 

II。走査線と面積を見つける(私は計画を立てていないので、以下の写真は、ロサンゼルス溶液から問題バレーにあります)

  私たちは、この事を見てください。

  

  私たちは、面全体を掃引するように、下から、走査線をシミュレートします。

  これは、横方向のセグメントの面で何かを更新するために走査線を停止します。その後、全体のグラフは、4つの線分を識別することができます。

  図:

  我々はそれを更新する必要がありますか?もちろん、線分の長さを計算しました。

  だから我々は記録する必要がある最初の事は決定されます。各線分の端点の座標に関する。

  その後、我々はX []それという配列の中にこれらの座標を置きます。

  この種のものが必要とされています。具体的な理由は、上読んでください。

  だから我々は、それが空である領域が、存在する場合にどのようにあなたが知っている、走査線がプロセスを単調考えますか?

  私たちは、矩形が、その後、走査線領域のこのグラフィックは飲み込まれた後、底に会い下にし、プロセス、長方形のベースに、常に最初の出会いの単調アップスキャンラインの末尾にあると思います。

  だから我々は、走査線が部分を持っているように、基板が-1権利をスイープ値が割り当てられている場合、基板が1に割り当てられている場合、各横の行に重みを割り当てるために、第二のものを記録する必要があります私たちは、面積を計算します。

 

  その後、我々は問題領域を考慮し、我々は2つの矩形の領域の一部は一度しか計算でき交差することを知っています。

  2つの長方形、他の長方形のエッジに少なくとも1つの点の長方形の側縁との交点。

  次に図X [1]および組成X [3]のように左右のセグメントのエンドポイントが二つの部分に分割することができるように、X [1]、X [2]及び組成セグメントX [2]、X [3]のセグメントから構成される。

  こうしてパターンが横方向に横断エッジ4、長手部3は4つの縦方向縁部に分割されている三つの部分に分けました。

  しかし、走査線を下から上に掃引され、唯一それを行うにはどのように分割横エッジ、縦方向の側面に対処することができますか?

  

  その後、我々は、ツリーラインを使用して考えることができます。

 

  左右ポイント - - 右エンドポイントを、エンドポイントが同じグループの周りに非することができるセグメントを格納するために各ノードと我々セグメントツリーは、このセグメントを残すことができるエンドポイントの約グループに切断必ずしもありません。

  次の図は、私たちは、このグラフのツリーラインのように構築することができます。

  上記の文章のために我々は明らかにチャートで見ることができます。

  当一条线段被扫描线扫到的时候,立即更新线段树每个节点维护的线段的覆盖长度和权值。

  比如扫到最下面这条线段的时候,线段树1,2节点维护的线段覆盖长度和权值就会被更新。

  扫到下数第二条线段的时候,1,2,3,5,6节点维护的线段覆盖长度和权值就会被更新。

  那么不难看出线段树所维护的左右节点实际上是线段的编号,另外维护线段覆盖长度和权值。

  这样扫描线扫有权值的部分就有我们要计算的面积,那么就更新线段覆盖长度。

  那么对于每一条线段我们也记录它的左右端点和纵坐标以及权值,按照纵坐标优先升序排序,保证扫描线从下向上扫。

  

  还剩下一个问题,X[]数组。

  不难看出这个X[]数组排序的意义是为了方便在线段树更新节点线段覆盖长度的时候直接减就行了。

  另外,相同的X[]我们其实只需要一个就够了,所以我们要离散化处理。

  这样最后就可以进行计算面积了。

  S=Σ线段覆盖长度*扫过的高度,即纵坐标的差。

 

  代码如下:(我觉得上面没理解透彻的同学看下代码也应该理解得差不多了吧)

  题目:洛谷【模板】扫描线

#include<bits/stdc++.h>
using namespace std;
struct Segment//从下向上的扫描线 那么就是横向的线段 
{
    long long l,r,h;//每一条线段的左端点坐标 右端点坐标 纵坐标。
              //其中纵坐标的意义是在读到它对应的线段 也就是对于一个矩形的下底 读到它的上底时更改val 
    int val;//val的意义就是在下底使这一个线段权值加1 表示扫描线向下扫的时候 保证这条线段的下方是覆盖的
            //那么在遇到上底时权值为-1 也就是说覆盖结束了 
            //对于整个平面 扫描线所加的面积就是扫过的 并且有权值的位置 
    bool operator < (const Segment &k) const
    {
        return h<k.h;
    }//重载运算符 使得纵坐标小的线段 也就是每个矩形的下底优先被扫描线读到 
}Seg[800010];//空间要开足够大 每个矩形最少有两条线段 所以最少应该开两倍 
struct SegTree//线段树部分
{
    int l,r;//线段树的左右节点 分别用来存储 一条横向线段的编号
            //这里注意 线段树每个节点存储的不是线段的左右端点 而是对于一对左右端点截出的线段
            //并且 这里的左右节点并不严格在输入时对应一条线段 但它们之间一定是有一条线段
            /*就比如说 3号节点所存的线段可能是第二条线段的右端点和第三条线段的右端点所截成的线段 
              因为第二条线段和第三条线段的纵坐标相同 所以它们有交集但不完全重合 可能会出现这样的情况*/ 
    int sum;//这里的sum表示线段树节点的权值 也就是说被覆盖的次数
            //但由于是求面积并 所以被覆盖一次还是两次并没有区别 只是有权值和没权值的区别
            //有权值的线段 在扫描线扫过的时候就会计算面积 否则就不会计算面积 
    long long len;//这是每一个线段树节点所代表的线段的长度 计算面积时使用 
}Tree[1600010];//空间要开足够大  一个线段树四倍 最好开到八倍会好一点 
long long X[800010];
long long n,x1,x2,yy,y2,ans;
void Build(int k,long long l,long long r)
{
    Tree[k].l=l;
    Tree[k].r=r;
    Tree[k].sum=0;//扫描线没来之前没有权值 权值变更是在扫描线到达某一条线段时修改 
    Tree[k].len=0;//同上 
    if(l==r)
    {
        return;
    }
    int mid=l+r>>1;
    Build(k<<1,l,mid);
    Build(k<<1|1,mid+1,r);
}
void pushup(int k)
{
    int l=Tree[k].l,r=Tree[k].r;
    if(Tree[k].sum)//这就是上面所说的 只有被覆盖和没被覆盖的区别
    {
        Tree[k].len=X[r+1]-X[l];//如果线段被扫描线扫到了 则更新扫到的线段长度 
    } 
    else
    {
        Tree[k].len=Tree[k<<1].len+Tree[k<<1|1].len;//如果没被扫到 则从以前扫过的线段更新 
    }
}
void update(int k,long long L,long long R,int val)//当扫描线扫到一条线段 执行此操作 L,R分别记录线段的左右端点坐标 val代表这是下底还是上底
{
    int l=Tree[k].l,r=Tree[k].r;
    if(X[r+1]<=L || R<=X[l])//要更新的线段不属于这个线段树节点 算是个剪枝吧 
    {
        return;
    }
    if(L<=X[l] && X[r+1]<=R)
    {
        Tree[k].sum+=val;//更新 如果是下底 那么sum就有了值 代表扫描线往上的地方需要计算面积
                         //如果是上底 那么sum不一定没有值 只是减少 代表它对应的矩形面积计算完毕
        pushup(k);
        return; 
    }
    //由于有剪枝 我们就不用判断了 直接修改子树就好了
    update(k<<1,L,R,val);
    update(k<<1|1,L,R,val);
    pushup(k);
} 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld%lld%lld%lld",&x1,&yy,&x2,&y2);
        X[2*i-1]=x1,X[2*i]=x2;
        Seg[2*i-1]=(Segment){x1,x2,yy,1};//记录线段的信息 
        Seg[2*i]=(Segment){x1,x2,y2,-1};//按照这个操作 开两倍是正常的 
    }
    n<<=1;//线段数是矩形数二倍 
    sort(Seg+1,Seg+n+1);//按照纵坐标升序排序 
    sort(X+1,X+n+1);//相同的横坐标我们只需要一次 
    int cnt=unique(X+1,X+n+1)-X-1;//因此通过离散化来确定不同横坐标的数量 
    Build(1,1,cnt-1);//这里cnt要-1 因为我们cnt代表横坐标的数量 
                     //而线段树存储的是线段数量 根据植树问题 线段数=点数-1
    for(int i=1;i<n;i++) //最后一条边不需要计算和更新了吧
    {
        update(1,Seg[i].l,Seg[i].r,Seg[i].val);
        ans+=Tree[1].len*(Seg[i+1].h-Seg[i].h);//计算面积
    } 
    printf("%lld",ans);
    return 0;
}
View Code

 

三.扫描线求周长并

  待补充~

  其实可以理解一下 是扫描线左右扫一次和上下扫一次~

  

おすすめ

転載: www.cnblogs.com/Rakan-LoveJ/p/11401851.html