【轉換模型+掃描線】【UVA1398】Meteor

The famous Korean internet company nhn has provided an internet-based photo service which allows The famous Korean internet company users to directly take a photo of an astronomical phenomenon in space by controlling a high-performance telescope owned by nhn. A few days later, a meteoric shower, known as the biggest one in this century, is expected. nhn has announced a photo competition which awards the user who takes a photo containing as many meteors as possible by using the photo service. For this competition, nhn provides the information on the trajectories of the meteors at their web page in advance. The best way to win is to compute the moment (the time) at which the telescope can catch the maximum number of meteors. 

You have n meteors, each moving in uniform linear motion; the meteor mi moves along the trajectory pi + t×vi over time t , wheret is a non-negative real value, pi is the starting point of mi and vi is the velocity of mi . The point pi = (xiyi) is represented by X -coordinate xi and Y -coordinate yi in the (XY) -plane, and the velocity vi = (aibi) is a non-zero vector with two components ai and bi in the (XY) -plane. For example, if pi = (1, 3) and vi = (-2, 5) , then the meteor mi will be at the position (0, 5.5) at time t = 0.5 because pi + t×vi = (1, 3) + 0.5×(-2, 5) = (0, 5.5) . The telescope has a rectangular frame with the lower-left corner (0, 0) and the upper-right corner (wh) . Refer to Figure 1. A meteor is said to be in the telescope frame if the meteor is in the interior of the frame (not on the boundary of the frame). For exam! ple, in Figure 1, p2,p3p4 , and p5 cannot be taken by the telescope at any time because they do not pass the interior of the frame at all. You need to compute a time at which the number of meteors in the frame of the telescope is maximized, and then output the maximum number of meteors.ios

\epsfbox{p3905.eps}

Input 

Your program is to read the input from standard input. The input consists of T test cases. The number of test cases T is given in the first line of the input. Each test case starts with a line containing two integers w and h (1$ \le$wh$ \le$100, 000) , the width and height of the telescope frame, which are separated by single space. The second line contains an integer n , the number of input points (meteors), 1$ \le$n$ \le$100, 000 . Each of the next n lines contain four integers xiyiai , and bi ; (xiyi) is the starting point pi and (aibi) is the nonzero velocity vector vi of the i -th meteor; xi and yi are integer values between -200,000 and 200,000, and ai and bi are integer values between -10 and 10. Note that at least one of ai and bi is not zero. These four values are separated by single spaces. We assume that all starting points pi are distinct.web

Output 

Your program is to write to standard output. Print the maximum number of meteors which can be in the telescope frame at some moment.算法


2 
4 2 
2 
-1 1 1 -1 
5 2 -1 -1 
13 6 
7 
3 -2 1 3 
6 9 -2 -1 
8 0 -1 -1 
7 6 10 0 
11 -2 2 1 
-2 4 6 -1 
3 2 -5 -1



1 
2


 一.轉化模型
  很容易就發現,流星經過鏡框的時間是一段區間。
  那麼問題能夠等價於 找一個點,使他最多被多少個區間包含,輸出區間數量最大值


 二.離散化+掃描線
  將點離散化存儲,而且排序。當掃描線掃過左端點 now+1 右端點 now-1 其中出現的最大的now即爲ans

 注意:
   若是時間相同的話 先右端點,後左端點。

#include <cstdio>  
#include <cstdlib>  
#include <cmath>  
#include <cstring>  
#include <ctime>  
#include <algorithm>  
#include <iostream>
#include <sstream>
#include <string>
#define oo 2000000;
#define maxn 100001   
using namespace std;
struct point
{
	double x;
	int flag;
};
point P[maxn*10]; int tot=0;
int w,h,n;
int x,y,a,b;
int getQuJian()
{
	double t1,t2;
	double t3,t4;
	if(a!=0) 
	 t1=((double)(-x)/a),t2=(double)(w-x)/a;
	else 
	   {
	    if (0<x&&x<w) { t1=0,t2=oo;}
	    else 
	   	{
	   		t1=0;t2=0;
	   	}
	   }
	
	if(b!=0) 
	   t3=((double)(-y)/b),t4=(double)(h-y)/b;
	else 
	      {
	      if(0<y&&y<h){t3=0,t4=oo;}
		  else t3=0,t4=0;
		  }
	if(a<0) swap(t1,t2);
	if(b<0) swap(t3,t4);
	if(t1>t2) return 0;
	if(t3>t4) return 0;
	if(t1<=t3)
	{
		if(t3<=t2)
		 {
		 	if(t2<=t4)
		 	{
		 		if(t2!=t3)
		 		{
		 		if(t3<0&&t2>0) t3=0;
		 		P[++tot].x=t3;P[tot].flag=0;
		 		P[++tot].x=t2;P[tot].flag=1;
				}	
			}
			else 
			{
				if(t3!=t4)
				{
				if(t3<0&&t4>0) t3=0;
				P[++tot].x=t3;P[tot].flag=0;
				P[++tot].x=t4;P[tot].flag=1;
				}
			}
		 }
		else;
	}
	else
	{
		if(t1<=t4)
		{
		   if(t4<=t2)
		   {
		   		if(t1!=t4)
				{
				if(t1<0&&t4>0) t1=0;
				P[++tot].x=t1;P[tot].flag=0;
		 		P[++tot].x=t4;P[tot].flag=1;
		   		}
		   }
		   else
		   {
		   		if(t1!=t2)
		   		{
		   		if(t1<0&&t2>0) t1=0;
		   		P[++tot].x=t1;P[tot].flag=0;
		   		P[++tot].x=t2;P[tot].flag=1;
		   		}
		   }
		}
	}
	return 0;
}
int cmp(const void *i,const void *j)
{
	point *ii=(point *)i,*jj=(point *)j;
	if(ii->x!=jj->x) 
		if(ii->x>jj->x) return 1;
		else return -1;
	else return jj->flag-ii->flag;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	int T,ans,now;
	cin>>T;
	while(T--)
	{
		tot=0,ans=0,now=0;
		cin>>w>>h;
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>x>>y>>a>>b;
			getQuJian();
		}
		qsort(P+1,tot,sizeof(P[1]),cmp);
		for(int i=1;i<=tot;i++)
			{
				if(P[i].x>0)
				{
					if(P[i].flag==1) now--;
					else 
					{
						now++;
						ans=max(ans,now);
					}
				}
				else if(P[i].x==0&&P[i].flag==0)
				{
						now++;
						ans=max(ans,now);
				}
			}
		cout<<ans<<endl;
	}
	return 0;
}
  

劉汝佳老師的求交集寫的更優美(左端點與左端點比,右端點與右端點比,而且初始區間爲【0,+oo】,使得沒有我那麼多麻煩的判斷)

void update(int x, int a, int w, double& L, double& R) {
  if(a == 0) {
    if(x <= 0 || x >= w) R = L-1; //無解
  } else if(a > 0) {
    L = max(L, -(double)x/a);
    R = min(R, (double)(w-x)/a);
  } else {
    L = max(L, (double)(w-x)/a);
    R = min(R, -(double)x/a);
  }
}


劉汝佳老師的整個題目解釋:

【分析】ide

不難發現,流星的軌跡是沒有直接意義的,有意義的只是每一個流星在照相機視野內出現的時間段。換句話說,咱們把本題抽象爲這樣一個問題:給出n個開區間(Li,Ri),你的任務是求出一個數t,使得包含它的區間數最多(爲何是開區間呢?請讀者思考)。開區間(Li,Ri)是指全部知足Li<x <Ri的實數x的集合。函數

把全部區間畫到平行於數軸的直線上(省得相互遮擋,看不清楚),而後想象有一條豎直線從左到右進行掃描,則問題能夠轉化爲:求掃描線在哪一個位置時與最多的開區間相交,如圖1-27所示。this

圖  1-27spa

不難發現,當掃描線移動到某個區間左端點的「右邊一點點」時最有但願和最多的開區間相交(想想,爲何)。爲了快速得知在這些位置時掃描線與多少條線段相交,咱們再一次使用前面提到的技巧:維護信息,而不是從新計算。code

咱們把「掃描線碰到一個左端點」和「掃描線碰到一個右端點」當作是事件(event),則掃描線移動的過程就是從左到右處理各個事件的過程。每遇到一個「左端點事件」,計數器加1;每遇到一個「右端點事件」,計數器減1。這裏的計數器保存的正是咱們要維護的信息:掃描線和多少個開區間相交,如圖1-28所示。component


圖  1-28orm

這樣,咱們能夠寫出這樣一段僞代碼。

 

將全部事件按照從左到右排序

while(還有未處理的事件) {

  選擇最左邊的事件E

  if(E是「左端點事件」) { cnt++; if(cnt > ans) ans= cnt; } //更新計數器和答案

  else cnt--;//必定是「右端點事件」

}

這段僞代碼看上去挺有道理,但實際上暗藏危險:若是不一樣事件的端點相同,那麼哪一個排在前面呢?考慮這樣一種狀況——輸入是兩個沒有公共元素的開區間,且左邊那個區間的右端點和右邊那個區間的左端點重合。在這種狀況下,兩種排法的結果大相徑庭:若是先處理左端點事件,執行結果是2;若是先處理右端點事件,執行結果是1。這纔是正確答案。

這樣,咱們獲得了一個完整的掃描算法:先按照從左到右的順序給事件排序,對於位置相同的事件,把右端點事件排在前面,而後執行上述僞代碼的循環部分。若是你對這個衝突解決方法心存疑慮,不妨把它理解成把全部區間的右端點往左移動了一個極小(但大於0)的距離。




#include<cstdio>
#include<algorithm>
using namespace std;
//0<x+at<w
void update(int x, int a, int w, double& L, double& R) {
  if(a == 0) {
    if(x <= 0 || x >= w) R = L-1; //無解
  } else if(a > 0) {
    L = max(L, -(double)x/a);
    R = min(R, (double)(w-x)/a);
  } else {
    L = max(L, (double)(w-x)/a);
    R = min(R, -(double)x/a);
  }
}

const int maxn = 100000 + 10;

struct Event {
  double x;
  int type;
  bool operator < (const Event& a) const {
    return x < a.x || (x == a.x && type > a.type); //先處理右端點
  }
} events[maxn*2];

int main() {
  int T;
  scanf("%d", &T);
  while(T--) {
    int w, h, n, e = 0;
    scanf("%d%d%d", &w, &h, &n);
    for(int i = 0; i < n; i++) {
      int x, y, a, b;
      scanf("%d%d%d%d", &x, &y, &a, &b);
      //0<x+at<w, 0<y+bt<h, t>=0
      double L = 0, R = 1e9;
      update(x, a, w, L, R);
      update(y, b, h, L, R);
      if(R > L) {
        events[e++] = (Event){L, 0};
        events[e++] = (Event){R, 1};
      }
    }
    sort(events, events+e);
    int cnt = 0, ans = 0;
    for(int i = 0; i < e; i++) {
      if(events[i].type == 0) ans = max(ans, ++cnt);
      else cnt--;
    }
    printf("%d\n", ans);
  }
  return 0;
}

另外,本題還能夠徹底避免實數運算,所有采用整數:只須要把代碼中的double所有改爲int,而後在update函數中把全部返回值乘以lcm(1,2,…,10)=2 520便可(想想,爲何)。

 由於2520 是他們的公倍數 除出來一定是整數

void update(int x, int a, int w, int& L,int& R) {
  if(a == 0){
    if(x<= 0 || x >= w) R = L-1; //無解
  } else if(a> 0) {
    L =max(L, -x*2520/a);
    R =min(R, (w-x)*2520/a);
  } else {
    L =max(L, (w-x)*2520/a);
    R =min(R, -x*2520/a);
  }
}
相關文章
相關標籤/搜索