1.7 光影切割问题 

1、问题描述:


     不少人很爱玩游戏,例如 CS ⑨。 游戏设计也成为程序开发的热点之一,


我们假设要设计破旧仓库之类的场景作为战争游戏的背景。仓库的地面会因为阳光从屋顶的漏洞或者窗口照射进来而形成许多光照区域和阴影区域。


为了简单起见,假设不同区域的边界都是直线 ⑩, 我们把这些直线都叫做“光影线”,并且不存在三条光影线相交于一点的情况。


     那么,如果我们需要快速计算某个时刻,在 X 坐标[ A, B] 区间的地板上被光影划分成多少块。如何才能写出算法来计算呢? 


2、解法一:


1、首先观察2条直线最多把区间分成4个部分。


2、对于三条直线,若三条直线两个交点分成6个部分,若三个交点分成7个部分;


3、如下规律:


2条直线--> 1个交点 --> 空间分成4个部分 


3条直线--> 2个交点 --> 空间分成6个部分


3条直线--> 3个交点 --> 空间分成7个部分


4、每增加一条直线,增加m个交点,那么这条直线被新增加的m个交点分成m+1段,每条直线把原来区域分成2块, 因此,新增加m+1块新区域;所以N条直线,M个交点,共N+M+1;


 


因此该问题就转化为求直线交点个数的问题。


3、解法二:逆序数 


更为巧妙,进一步将问题转化为求逆序数问题。但书中没有给出如何求解逆序数。 





HDOJ 3465 逆序数 


Line都是直线,不是线段,没有端点,输入的点只是用于确定这条直线的位置 


对每条直线与x=L,和x=r的交点分别为xl,xr,两直线在(L,R)相交,必然有 


xl1>xl2&&xr1<xr2 或者 xl1<xl2&&xr1>xr2,(A.L-B.L)*(A.R-B.R)<0



如果将与L的交点从小到大排,那么当相应的R的交点出现一个逆序数对就表示有两直线有交点.


先将所有直线根据l递增排序,之后编号1~n,再根据r递减排序,得到一个编号序列。


例如3412,递减,其中r3>r4>r1>r2, 又编号:l1<l2<r3<r4,1234=>所以12 34 所以3和4有交点,1和2有交点。


 这符合逆序数的关系:一个数的逆序数是在它之前比他大的数的个数,


  


当然这里是小的数,原因是为了方便树状数组处理。所以只要根据上述方法排序再求逆序数即可。 


1.与y轴平行得线,只要这样的线在(l,r)范围内,则必定跟别的不平行线相交。


所以计算下个数在乘积 


2.l和r相同的情况。当l相同时,r递增排序;当r相同时,l递减排序。


就能使在l和r上的交点不计算在内。 



参考别人的树状数组


    树状数组可以说是线段树的简化,处理单一,但写起来简便。 


    两个函数: 


    add(x,val),时c[x]+=val; 


    sum(x),求c[1]+c[2]+...+c[x];  


#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;

#define lowbit(x) (x&(-x))
const int N=5e4+10;
struct node{
double a,b;
int num;
};
node e[N];
int c[N];
double l,r;

int cmp1(node x,node y) //left 递增排序
{
if(x.a==y.a)
return x.b<y.b;
return x.a<y.a;
}

int cmp2(node x,node y) //right 递减排序
{
if(x.b==y.b)
return x.a>y.a;
return x.b>y.b;
}

void add(int x,int val)
{
while(x<N)
{
c[x]+=val;
x+=lowbit(x);
}
}

int sum(int x)
{
int ans=0;
while(x>0)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}

int main()
{
int n;
int t,tt,i,j,ans;
double x1,y1,x2,y2,k,b;

while(scanf("%d",&n)!=EOF)
{
t=tt=0;
ans=0;
scanf("%lf%lf",&l,&r);
for(i=0;i<n;i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);//两个点的横纵坐标
if(x1==x2)//横坐标相等 表示竖直的线 平行与y轴
{
if(l<x1&&x1<r)
tt++;
continue;
}
k=(y2-y1)/(x2-x1);//y=kx+b
b=y1-k*x1;
e[t].a=l*k+b;
e[t++].b=r*k+b;
}
sort(e,e+t,cmp1);
for(i=0;i<t;i++)//编号
e[i].num=i+1;
sort(e,e+t,cmp2);//递减排序 3412
memset(c,0,sizeof(c));
for(i=0;i<t;i++)//统计
{
add(e[i].num,1);
ans+=sum(e[i].num-1);
}
printf("%d\n",ans+tt*t);//加上平行y轴的直线所产生的交点
}
}