https://codeforces.com/gym/102770/problem/H
题意:
在二维平面上给定一些点一些线段,定义在x轴上一个点u,如果点u和任意一个给定点v的连线和所有给定线段都不相交(包括端点),则u不在阴影中。问x轴上阴影的长度(>1e9则输出-1)。
思路:
首先对每个点预处理出会被线段遮挡的范围。考虑一个点被一条线段遮挡,产生的阴影是一条线段或者一个射线,因为题目要求最后答案>1e9则输出-1,而且给定的点都是整点,因此可以将射线转为一个端点 为INF(1e12)的线段。一个点被遮挡的范围就是它被所有线段遮挡产生的线段的并。求出每个点遮挡范围之后,对所有点的遮挡范围取交集,交集中的长度就是答案。关键就是如何求线段并和线段集合的交。
解法1:
求线段并:将线段按左端点排序,从前往后遍历,依次将新的线段和已有的最右边的线段合并即可,保证每次合并后线段之间没有重复区间。
求线段集合的交:对所有线段集合中的所有线段左右端点进行离散化,建立线段树,对每条线段而言就是在线段树的对应区间上+1,最后询问线段树上哪些区间最大值=n,最小值=n。
#include <bits/stdc++.h>
#define rson rt<<1|1
#define lson rt<<1
#define mid ((l+r)>>1)
using namespace std;
const int maxn=505;
const double eps = 1e-6;
const double INF=1e18;
int sgn(double x) {
if(fabs(x) < eps)
return 0;
if(x < 0)
return -1;
return 1;
}
struct Point {
double x,y;
Point() {}
Point(double _x,double _y) {
x = _x;
y = _y;
}
Point operator -(const Point &b)const {
return Point(x - b.x,y - b.y);
}
Point operator +(const Point &b)const {
return Point(x + b.x,y +b.y);
}
double operator ^(const Point &b)const {
return x*b.y - y*b.x;
}
double operator *(const Point &b)const {
return x*b.x + y*b.y;
}
};
double xmult(Point p0,Point p1,Point p2) { //p0p1 X p0p2
return (p1-p0)^(p2-p0);
}
struct Line{
Point s,t;
Line(Point X=Point(),Point Y=Point()){
s=X,t=Y;
}
};
Point getIntersectPoint(Line a, Line b) {
double a1 = a.s.y - a.t.y, b1 = a.t.x - a.s.x, c1 = a.s.x * a.t.y - a.t.x * a.s.y;
double a2 = b.s.y - b.t.y, b2 = b.t.x - b.s.x, c2 = b.s.x * b.t.y - b.t.x * b.s.y;
return Point((c1*b2-c2*b1)/(a2*b1-a1*b2), (a2*c1-a1*c2)/(a1*b2-a2*b1));
}
Point p[maxn];
Line l[maxn];
struct XD{
double l,r;
}xd[maxn][maxn];
int cnt[maxn];
bool pequal(Point &a,Point &b){
Point temp=a-b;
if(sgn(temp.x)==0 && sgn(temp.y)==0)
return 1;
else
return 0;
}
XD getShadow(Point a,Line b){
//特判a在b端点上
if(pequal(a,b.s)||pequal(a,b.t)){
return{-INF,INF};
}
Point pp[]={b.s,b.t};
Line x={Point(0,0),Point(10000,0)};
if(pp[0].y>pp[1].y)swap(pp[0],pp[1]);
//特判a在b上
if(pp[0].y<a.y && a.y<pp[1].y){
if(sgn((a-pp[0])^(pp[1]-a))==0){
return {-INF,INF};
}
}
if(a.y>pp[1].y){
Line l1={a,pp[0]};
Line l2={a,pp[1]};
double xx[2];
xx[0]=getIntersectPoint(l1,x).x;
xx[1]=getIntersectPoint(l2,x).x;
if(xx[0]>xx[1])swap(xx[0],xx[1]);
return {xx[0],xx[1]};
}
else if(a.y>pp[0].y){
Line l2={a,pp[0]};
double xx=getIntersectPoint(l2,x).x;
if(xmult(pp[0],pp[1],a)<0){//直线在点左边
return {-INF,xx};
}
else{
return {xx,INF};
}
}
else{
return {0,0};
}
}
bool cmp(XD a,XD b){
return a.l<b.l;
}
double lisan[(maxn*maxn*2)];
int lisancnt=0;
//区间加,区间最大/最小
int Tmax[(maxn*maxn*2)<<2],Tmin[(maxn*maxn*2)<<2];
int lazy[(maxn*maxn*2)<<2];
void push_up(int rt,int l,int r){
Tmax[rt]=max(Tmax[lson],Tmax[rson]);
Tmin[rt]=min(Tmin[lson],Tmin[rson]);
}
void push_down(int rt,int l,int r){
if(!lazy[rt])return;
Tmax[lson]+=lazy[rt];Tmax[rson]+=lazy[rt];
Tmin[lson]+=lazy[rt];Tmin[rson]+=lazy[rt];
lazy[lson]+=lazy[rt];lazy[rson]+=lazy[rt];
lazy[rt]=0;
}
void add(int rt,int l,int r,int ql,int qr,int value){
if(l>qr || r<ql)return;
if(ql<=l && r<=qr){
lazy[rt]+=value;
Tmax[rt]+=value;
Tmin[rt]+=value;
return;
}
push_down(rt,l,r);
if(r==l+1)return;//到叶子节点
if(ql<=mid-1)add(lson,l,mid,ql,qr,value);
if(qr>=mid+1)add(rson,mid,r,ql,qr,value);
push_up(rt,l,r);
}
double query(int rt,int l,int r,int value){
double ans=0;
if(Tmax[rt]==value && Tmin[rt]==value){
ans+=lisan[r]-lisan[l];
return ans;
}
push_down(rt,l,r);
if(Tmax[lson]==value)ans+=query(lson,l,mid,value);
if(Tmax[rson]==value)ans+=query(rson,mid,r,value);
return ans;
}
void init(int n,int m){
memset(Tmax,0,sizeof(int)*((n*m*2)*4+2));
memset(Tmin,0,sizeof(int)*((n*m*2)*4+2));
memset(lazy,0,sizeof(int)*((n*m*2)*4+2));
lisancnt=0;
}
int main () {
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
init(n,m);
for(int i=1;i<=n;i++){
scanf("%lf%lf",&p[i].x,&p[i].y);
}
for(int i=1;i<=m;i++){
scanf("%lf%lf%lf%lf",&l[i].s.x,&l[i].s.y,&l[i].t.x,&l[i].t.y);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
xd[i][j]=getShadow(p[i],l[j]);
}
sort(xd[i]+1,xd[i]+1+m,cmp);
cnt[i]=1;
for(int j=2;j<=m;j++){
if(xd[i][cnt[i]].r>xd[i][j].l){//有重复区域直接合并
xd[i][cnt[i]].r=max(xd[i][cnt[i]].r,xd[i][j].r);
}
else{
xd[i][++cnt[i]]=xd[i][j];
}
}
for(int j=1;j<=cnt[i];j++){
lisan[++lisancnt]=xd[i][j].l;
lisan[++lisancnt]=xd[i][j].r;
}
}
sort(lisan+1,lisan+1+lisancnt);
for(int i=1;i<=n;i++){
for(int j=1;j<=cnt[i];j++){
int left=lower_bound(lisan+1,lisan+1+lisancnt,xd[i][j].l)-lisan;
int right=lower_bound(lisan+1,lisan+1+lisancnt,xd[i][j].r)-lisan;
add(1,1,lisancnt,left,right,1);
}
}
double ans=query(1,1,lisancnt,n);
if(ans>=1e9){
puts("-1");
}
else
printf("%.10f
",ans);
}
}
解法2:
求线段并:建立map<double,int> m1,对于所有线段在m1上进行差分,即m1[l]++,m1[r]--,最后遍历m1,前缀和>0开始至前缀和=0这样的区间就是最终覆盖范围中的一个线段。
求线段集合的交:建立map<double,int> m2,对于所有线段集合中所有线段进行差分,即m2[l]++,m2[r]--,之后遍历m2,前缀和=n开始至前缀和<n这样的区间就是一个阴影区间,直接对答案加上贡献即可。
#include <bits/stdc++.h>
#define rson rt<<1|1
#define lson rt<<1
#define mid ((l+r)>>1)
using namespace std;
const int maxn=505;
const double eps = 1e-6;
const double INF=1e18;
int sgn(double x) {
if(fabs(x) < eps)
return 0;
if(x < 0)
return -1;
return 1;
}
struct Point {
double x,y;
Point() {}
Point(double _x,double _y) {
x = _x;
y = _y;
}
Point operator -(const Point &b)const {
return Point(x - b.x,y - b.y);
}
Point operator +(const Point &b)const {
return Point(x + b.x,y +b.y);
}
double operator ^(const Point &b)const {
return x*b.y - y*b.x;
}
double operator *(const Point &b)const {
return x*b.x + y*b.y;
}
};
double xmult(Point p0,Point p1,Point p2) { //p0p1 X p0p2
return (p1-p0)^(p2-p0);
}
struct Line{
Point s,t;
Line(Point X=Point(),Point Y=Point()){
s=X,t=Y;
}
};
Point getIntersectPoint(Line a, Line b) {
double a1 = a.s.y - a.t.y, b1 = a.t.x - a.s.x, c1 = a.s.x * a.t.y - a.t.x * a.s.y;
double a2 = b.s.y - b.t.y, b2 = b.t.x - b.s.x, c2 = b.s.x * b.t.y - b.t.x * b.s.y;
return Point((c1*b2-c2*b1)/(a2*b1-a1*b2), (a2*c1-a1*c2)/(a1*b2-a2*b1));
}
Point p[maxn];
Line l[maxn];
struct XD{
double l,r;
};
bool pequal(Point &a,Point &b){
Point temp=a-b;
if(sgn(temp.x)==0 && sgn(temp.y)==0)
return 1;
else
return 0;
}
XD getShadow(Point a,Line b){
//特判a在b端点上
if(pequal(a,b.s)||pequal(a,b.t)){
return{-INF,INF};
}
Point pp[]={b.s,b.t};
Line x={Point(0,0),Point(10000,0)};
if(pp[0].y>pp[1].y)swap(pp[0],pp[1]);
//特判a在b上
if(pp[0].y<a.y && a.y<pp[1].y){
if(sgn((a-pp[0])^(pp[1]-a))==0){
return {-INF,INF};
}
}
if(a.y>pp[1].y){
Line l1={a,pp[0]};
Line l2={a,pp[1]};
double xx[2];
xx[0]=getIntersectPoint(l1,x).x;
xx[1]=getIntersectPoint(l2,x).x;
if(xx[0]>xx[1])swap(xx[0],xx[1]);
return {xx[0],xx[1]};
}
else if(a.y>pp[0].y){
Line l2={a,pp[0]};
double xx=getIntersectPoint(l2,x).x;
if(xmult(pp[0],pp[1],a)<0){//直线在点左边
return {-INF,xx};
}
else{
return {xx,INF};
}
}
else{
return {0,0};
}
}
bool cmp(XD a,XD b){
return a.l<b.l;
}
int main () {
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lf%lf",&p[i].x,&p[i].y);
}
for(int i=1;i<=m;i++){
scanf("%lf%lf%lf%lf",&l[i].s.x,&l[i].s.y,&l[i].t.x,&l[i].t.y);
}
map<double,int>m2;
for(int i=1;i<=n;i++){
map<double,int>m1;
for(int j=1;j<=m;j++){
XD temp=getShadow(p[i],l[j]);
m1[temp.l]++;
m1[temp.r]--;
}
int temp=0,flag=0;
double pre=0;
for(auto x:m1){
temp+=x.second;
if(temp>0 && !flag){
pre=x.first;
flag=1;
}
else if(temp==0 && flag){
m2[pre]++;
m2[x.first]--;
flag=0;
}
}
}
int temp=0,flag=0;
double pre=0,ans=0;
for(auto x:m2){
temp+=x.second;
if(temp==n && !flag){
pre=x.first;
flag=1;
}
else if(temp<n && flag){
ans+=x.first-pre;
flag=0;
}
}
if(ans>1e9)
puts("-1");
else
printf("%.10f
",ans);
}
}