HDU 1255 覆盖的面积 (线段树扫描线+面积交)

自己YY了一个的写法,不过时间复杂度太高了,网上的想法太6了
题意:给你一些矩阵,求出矩阵的面积并

首先按照x轴离散化线段到线段树上(因为是找连续区间,所以段建树更加好做)。
然后我们可以想一下怎样才能使面积相交呢?我们可以注意到如果矩阵入线出现超过一次就一定有面积相交,所以我们记录入线与出线,再排序y轴,从小到大(注意y轴等大时先进后出)扫描线段。
记录:总长度,当前一整段一起覆盖一次的长度,当前一整段一起覆盖超过一次的长度,当前一整段一起覆盖覆盖的次数(注意这一整段的情况不会更新到孩子节点)为什么是当前一整段且不更新呢?因为我们要计算的是插入这一段(入线或出线)后,所有的线覆盖超过一次的长度。如果仅仅计算入线中超过一次线覆盖的长度,就会少计算一些面积,原因是我们每次会把y轴更新,这样就还有一些不在现在入线这一段中却覆盖超过一次的线段没有统计。
入线:矩阵下方的x轴的线(按照y轴扫描),表示矩阵进入扫描线,开始计算
出现:矩阵上方的x轴的线(按照y轴扫描),表示矩阵已经出去,不能计算了

/*给一些矩阵求矩阵面积交:离散化x轴,排序y轴扫描,注意记录一个cover,关键在此cover表示仅仅次一连续段的覆盖情况们不要更新到孩子节点
而此时只需要记录区间覆盖两次及以上的长度是多少就好*/
#include<map>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define mul(a,b) (a<<b)
#define dir(a,b) (a>>b)
const int Max=2010<<2;//每个矩形扫描时看做两条线
struct node
{
    double len[3];//线段本身长度,覆盖次数为1次,覆盖次数大于1次的线段的长度
    int cover;//表示这一段被覆盖的次数
}segtr[Max];
struct nide
{
    int typ;
    double xx1,xx2,yy1;
}lin[Max];
map<double,int> mp;//快速找到某个x所对应的树上的点
double llin[Max];//离散化到树上对应节点
bool cmp(struct nide p1,struct nide p2)
{
    if(p1.yy1==p2.yy1)
        return p1.typ>p2.typ;//关键 同一个位置先进后出
    return p1.yy1<p2.yy1;
}
void Create(int sta,int enn,int now)
{
    segtr[now].cover=0;
    segtr[now].len[0]=llin[enn]-llin[sta];
    segtr[now].len[1]=segtr[now].len[2]=0.0;
    if(sta+1==enn)//为了处理连续段可能没有包含完整的情况采用段建树
        return;
    int mid=dir(sta+enn,1);
    int next=mul(now,1);
    Create(sta,mid,next);
    Create(mid,enn,next|1);
    return;
}
void Upnow(int now,int sta,int enn)//主要的函数
{
        int next=mul(now,1);//注意现在的flag仅仅代表这一条线连续在一起的的情况,但是这一条线如果分成几段的话就不一定是这样的,即**这个flag并不代表这一线所以的情况**
        if(segtr[now].cover>=2)
        {
            segtr[now].len[1]=0.0;
            segtr[now].len[2]=segtr[now].len[0];
        }
        else if(segtr[now].cover==1)//此线段被覆盖一次,则孩子节点的线段覆盖情况就应该都加一次,注意此点与孩子节点的覆盖次数不相干
        {
            if(sta+1!=enn)
            segtr[now].len[2]=segtr[next].len[2]+segtr[next|1].len[2]+segtr[next].len[1]+segtr[next|1].len[1];
            else
            segtr[now].len[2]=0.0;
            segtr[now].len[1]=segtr[now].len[0]-segtr[now].len[2];//len 1+2 == 0
        }
        else
        {
            if(sta+1==enn)
            {
                segtr[now].len[1]=0.0;
                segtr[now].len[2]=0.0;
            }
            else
            {
                segtr[now].len[1]=segtr[next].len[1]+segtr[next|1].len[1];
                segtr[now].len[2]=segtr[next].len[2]+segtr[next|1].len[2];
            }
        }
    return;
}
void Update(int sta,int enn,int now,int x,int y,int z)
{
    if(sta>=x&&enn<=y)
    {
        segtr[now].cover+=z;
        Upnow(now,sta,enn);//更新len
        return;
    }
    int mid=dir(sta+enn,1);
    int next=mul(now,1);
    if(mid>x)
        Update(sta,mid,next,x,y,z);
    if(mid<y)
        Update(mid,enn,next|1,x,y,z);
    Upnow(now,sta,enn);
    return;
}
double Solve(int m,int cnt)
{
    double ans=0.0;
    sort(lin,lin+m,cmp);
    Create(1,cnt,1);
    for(int i=0;i<m;i++)//按照y轴从前到后扫描
    {
        if(i)
        ans+=segtr[1].len[2]*(lin[i].yy1-lin[i-1].yy1);//添加后计算,注意计算的时候要延迟一次
        Update(1,cnt,1,mp[lin[i].xx1],mp[lin[i].xx2],lin[i].typ);
    }
    return ans;
}
int main()
{
    int t,n,m,cnt;
    scanf("%d",&t);
    while(t--)
    {
        mp.clear();
        scanf("%d",&n);
        m=0;
        for(int i=0;i<n;i++)
        {
            scanf("%lf %lf %lf %lf",&lin[m].xx1,&lin[m].yy1,&lin[m+1].xx2,&lin[m+1].yy1);
            lin[m].xx2=lin[m+1].xx2,lin[m+1].xx1=lin[m].xx1;
            lin[m].typ=1,lin[m+1].typ=-1;//进线与出线
            mp[lin[m].xx1]=0,mp[lin[m].xx2]=0;
            m+=2;
        }
        cnt=1;//从1开始,关键关键
        for(map<double,int>::iterator it=mp.begin();it!=mp.end();++it)
        {
            it->second=cnt;
            llin[cnt++]=it->first;
        }
        cnt--;//注意不能多
        printf("%.2f\n",Solve(m,cnt));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值