NOIP模拟赛8.15----B、跨栏(扫描线+set)

跨栏

(jump.cpp/c/pas)

【问题描述】

在过去,校长曾经构思了许多新式学生运动项目的点子,其中就包括学生障碍赛,是学生们在赛道上跑越障碍栏架的竞速项目。他之前对推广这项运动做出的努力结果喜忧参半,所以他希望在他的操场上建造一个更大的学生障碍赛的场地,试着让这项运动更加普及。校长为新场地精心设计了N个障碍栏架,编号为1…N(2≤N≤105),每一个栏架都可以用这一场地的二维地图中的一条线段来表示。这些线段本应两两不相交,包括端点位置。

不幸的是,校长在绘制场地地图的时候不够仔细,现在发现线段之间出现了交点。然而,他同时注意到只要移除一条线段,这张地图就可以恢复到预期没有相交线段的状态(包括端点位置)。

请求出校长为了恢复没有线段相交这一属性所需要从他的计划中删去的一条线段。如果有多条线段移除后均可满足条件,请输出在输入中出现最早的线段的序号。

【输入格式】

输入的第一行包含N。余下N行每行用四个整数x1 y1 x2 y2表示一条线段,均为至多109的非负整数。这条线段的端点为(x1,y1)和(x2,y2)

。所有线段的端点各不相同。

【输出格式】

输出在输入中出现最早的移除之后可以使得余下线段各不相交的线段序号。

【输入输出样例】

jump.in

jump.out

4

2 1 6 1

4 0 1 5

5 6 5 5

2 7 1 3

2

 

 

注意:由于线段端点坐标数值的大小,在这个问题中你可能需要考虑整数类型溢出的情况

 

 

 

题解

考场思路

考虑找到了第一对相交线段,则答案一定是这一对线段的某一条,对这两条线段分别扫一下就好了

然后就O(n^2)找相交线段

发现T飞

于是加了一个“小小的”优化,按线段左端点的x坐标对线段进行排序

对于每一条线段i,当它与现在的线段j的x坐标无交集时,就可以停止判断(因为左端点递增,后面的线段也一定会与它无交集)

然后开始构造了一组数据卡自己,发现被卡掉了(当然会被卡掉),所以就又自闭了

后来也没管了(因为没时间写T3了,计算几何太久没打,一个跨立试验就写了40min……)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 100005
struct node{
	int ax,ay,bx,by,o;
}a[N];
bool cmp(node q,node w)
{
	return q.ax<w.ax||(q.ax==w.ax&&q.bx<w.bx);
}
struct point{
	int x,y;
	point(){}
	point(int a,int b){x=a;y=b;}
	point operator - (const point &t)const{
		return point(x-t.x,y-t.y);
	}
	long long operator * (const point &t)const{
		return 1ll*x*t.y-1ll*y*t.x;
	}
};
int deg(long long x)
{
	if(x>0) return 1;
	else if(x==0) return 0;
	else return -1;
}
int pd(node q,node w)
{
	if(max(w.ax,w.bx)<min(q.ax,q.bx)||min(w.ax,w.bx)>max(q.ax,q.bx)) return -1;
	if(max(w.ay,w.by)<min(q.ay,q.by)||min(w.ay,w.by)>max(q.ay,q.by)) return 0;
	point r=point(w.ax-q.ax,w.ay-q.ay),s=point(q.bx-q.ax,q.by-q.ay),t=point(w.bx-q.ax,w.by-q.ay);
	point x=point(q.ax-w.ax,q.ay-w.ay),y=point(w.bx-w.ax,w.by-w.ay),z=point(q.bx-w.ax,q.by-w.ay);
	if(deg(r*s)*deg(t*s)<=0&&deg(x*y)*deg(z*y)<=0)
		return 1;
	return 0;
}
int main()
{
	//freopen("2.in","r",stdin);
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	int n,i,j,k;
	n=gi();
	for(i=1;i<=n;i++){
		a[i].ax=gi();
		a[i].ay=gi();
		a[i].bx=gi();
		a[i].by=gi();
		a[i].o=i;
		if(a[i].ax>a[i].bx){
			swap(a[i].ax,a[i].bx);
			swap(a[i].ay,a[i].by);
		}
	}
	sort(a+1,a+n+1,cmp);
	for(i=1;i<n;i++){
		for(j=i+1;j<=n;j++){
			int tmp=pd(a[i],a[j]);
			if(tmp==-1)
				break;
			else if(tmp==1){
				for(k=1;k<=n;k++){
					if(k!=i&&k!=j){
						tmp=pd(a[j],a[k]);
						if(tmp==1){
							printf("%d",a[j].o);
							return 0;
						}
					}
				}
				for(k=1;k<=n;k++){
					if(k!=i&&k!=j){
						tmp=pd(a[i],a[k]);
						if(tmp==1){
							printf("%d",a[i].o);
							return 0;
						}
					}
				}
				printf("%d",min(a[j].o,a[i].o));
				return 0;
			}
		}
	}
}

考完下来90pts???懵逼

 

 

正解

扫描线+set裸题(好像还在ZROI讲过,可能当时自闭了没听到)

直接对每一个端点按先x后y排序

依次加进去,如果当前点是起始点,就把它在set里面lower_bound一下,找到与它相邻的两条线段,判断一下是否相交,然后把当前线段加进set

如果当前点是终止点,就把它弹出set,并判一下与它相邻的两条线段是否相交

找到了第一组相交线段时就直接退出,用上面的方法判断一下谁是解。

std:

#include <iostream>
#include <fstream>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
typedef long long LL;

int N;
double x;
struct Point { LL x, y; int segindex; };
struct Segment { Point p, q; int index; };

bool operator< (Point p1, Point p2) { return p1.x==p2.x ? p1.y<p2.y : p1.x<p2.x; }

// Intersection testing (here, using a standard "cross product" trick)
int sign(LL x) { if (x==0) return 0; else return x<0 ? -1 : +1; }
int operator* (Point p1, Point p2) { return sign(p1.x * p2.y - p1.y * p2.x); }
Point operator- (Point p1, Point p2) { Point p = {p1.x-p2.x, p1.y-p2.y}; return p; }
bool isect(Segment &s1, Segment &s2)
{
  Point &p1 = s1.p, &q1 = s1.q, &p2 = s2.p, &q2 = s2.q;
  return ((q2-p1)*(q1-p1)) * ((q1-p1)*(p2-p1)) >= 0 && ((q1-p2)*(q2-p2)) * ((q2-p2)*(p1-p2)) >= 0;
}

// What is the y coordinate of segment s when evaluated at x?
double eval(Segment s) {
  if (s.p.x == s.q.x) return s.p.y;
  return s.p.y + (s.q.y-s.p.y) * (x-s.p.x) / (s.q.x-s.p.x);
}
bool operator< (Segment s1, Segment s2) { return s1.index != s2.index && eval(s1)<eval(s2); }
bool operator== (Segment s1, Segment s2) { return s1.index == s2.index; }

int main(void)
{
  ifstream fin ("jump.in");
  vector<Segment> S;
  vector<Point> P;

  fin >> N;
  for (int i=0; i<N; i++) {
    Segment s;
    fin >> s.p.x >> s.p.y >> s.q.x >> s.q.y;
    s.index = s.p.segindex = s.q.segindex = i;
    S.push_back(s);
    P.push_back(s.p); P.push_back(s.q);
  }
  sort (P.begin(), P.end());

  set<Segment> active;
  int ans1, ans2; // sweep across scene to locate just one intersecting pair...
  for (int i=0; i<N*2; i++) {
    ans1 = P[i].segindex; x = P[i].x;
    auto it = active.find(S[ans1]);
    if (it != active.end()) {
      // segment already there -- we've reached its final endpoint.  check intersection with the
      // pair of segments that becomes adjacent when this one is deleted.
      auto after = it, before = it; after++;
      if (before != active.begin() && after != active.end()) {
	before--;
	if (isect(S[before->index], S[after->index]))
	  { ans1 = before->index; ans2 = after->index; break; }
      }
      active.erase(it);
    }
    else {
      // new segment to add to active set.
      // check for intersections with only the segments directly above and below (if any)
      auto it = active.lower_bound(S[ans1]);
      if (it != active.end() && isect(S[it->index], S[ans1])) { ans2 = it->index; break; }
      if (it != active.begin()) { it--; if (isect(S[it->index], S[ans1])) { ans2 = it->index; break; } }
      active.insert(S[ans1]);
    }
  }

  if (ans1 > ans2) swap (ans1, ans2);
  int ans2_count = 0;
  for (int i=0; i<N; i++)
    if (S[i].index != ans2 && isect(S[i], S[ans2])) ans2_count++;

  ofstream fout ("jump.out");
  fout << (ans2_count > 1 ? ans2+1 : ans1+1) << "\n";
  return 0;
}

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值