1. 程式人生 > >HDU 5581 Infinity Point Sets ACM/ICPC 2015 上海區域賽 I 計算幾何+組合計數

HDU 5581 Infinity Point Sets ACM/ICPC 2015 上海區域賽 I 計算幾何+組合計數

2015年上海區域賽的題目,這道題還是比較有趣的,反正我是WA哭了。。

題目在HDU上也有,連結:

題目的意思是,給出二維空間裡n個點的座標,求有多少個不同的子點集不是無限點集。無限點集的定義是,將點集中的點兩兩相連,線段產生的交點加入點集中,繼續上面的操作,如果操作能夠無限地進行下去,則稱之為無限點集。

首先我們可以分析,只有4個點或更少的點集一定不是無窮點集,如圖:

 

其次,5個點及以上的點集大概有以下三種情況:

 

在第一種情況中,五個藍色的點交點為五個新的紅色的點,這樣的操作顯然能夠無限地進行下去。

在第二種情況中,是有三個以上的點共線,還有兩個點分別線上段的兩邊,這樣一來生成的新的黑色的點也線上段上,操作不會無限地進行。

第三種情況中,是四個以上的點共線,還有一個點在外面,這種情況不會產生交點,於是操作也不會無限進行。

當然還有第四種情況,所以的點都共線。

但是在第二種情況中,會出現重複計算的狀況,比如下圖:

 

這種情況下,分別以AOC共線,BD在上下兩邊 和BOD共線,AC在左右兩邊,將第二種情況重複計算了一次。

綜上所述,答案由以下幾部分組成:

1.四個點及以下

2.五個及以上的共線的點

3.四個及以上共線的點+線外一個點

4.三個及以上共線的點+線外兩側各一個點

5.減去重複計算的第四部分

那麼實現方法就是,1直接用排列組合做了。

列舉每一個點i,對所以其他的點做對點i的極角排序,這樣就能找出共線的點,這種情況下情況

2就能計算出來。在極角排序之後同時統計直線兩側點的個數,就能夠計算出34。最後統計以點i為中心,每條直線兩個方向上各有多少個點,然後列舉兩兩直線,將重複的情況4給計算出來,然後減掉。

這樣這個問題就完全解決了。對於更具體的實現,計算直線兩側的點的個數 以及 直線上兩邊點的個數,直接列舉大於等於0的極角,然後二分查詢相反方向的角,就能很快地處理出來這些資料。

最後的結果要對10^9+7取膜,於是還有預處理出1-1000的逆元,在計算排列的時候除要用乘逆元來代替。

AC程式碼:

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define MAXN 1005
#define MOD 1000000007
#define LL long long
#define PI (acos(-1))
#define EPS 1e-10
using namespace std;

struct Point {
	double x, y;
	Point(double x = 0, double y = 0):x(x), y(y) {}
};

typedef Point Vector;

Vector operator - (Point A, Point B) { return Vector(A.x - B.x, A.y - B.y); }

LL C[MAXN], n;

inline int sign(double x) {
	if (fabs(x) < EPS) return 0;
	else return x < 0 ? -1 : 1;
}

inline double Dot(Vector A, Vector B) { return A.x*B.x + A.y*B.y; }//???? 
inline double Length(Vector A) { return sqrt(Dot(A, A)); }
inline double Cross(Vector A, Vector B) { return A.x*B.y - A.y*B.x; }
inline double Angle(Vector A, Vector B) {
	double res;
	res = acos(Dot(A, B) / Length(A) / Length(B));
	if (sign(PI - res) == 0 || Cross(B, A) >= 0) return res;
	else return -res;
}

inline LL Power(LL a, LL b) {
	LL ans = 1, now = a;
	if (a == 0) return 0;
	while (b != 0) {
		if (b & 1) {
			ans *= now;
			ans %= MOD;
		}
		now *= now;
		now %= MOD;
		b = b >> 1;
	}
	return ans;
}

inline LL c(LL a, LL b) {
	LL ans = 1;
	b = b < a - b ? b : a - b;
	for (int i = 0; i < b; i++) {
		ans *= a - i;
		ans %= MOD;
		ans *= C[i + 1];
		ans %= MOD;
	}
	return ans;
}

Point node[MAXN];
double beta[MAXN];
int s1[MAXN], e1[MAXN], s2[MAXN], e2[MAXN], len;
int tot, tot2;
Vector vx;

inline int comp(const void* a, const void *b) {
	return sign((*(double*)a) - (*(double*)b));
}

inline void Find(double v) {
	int l, r, mid;
	l = 0; r = tot;
	while (l != r) {
		mid = (l + r) / 2;
		if (sign(beta[mid] - v) >= 0) r = mid;
		else l = mid + 1;
	}
	s2[tot2] = l;
	l = 0; r = tot;
	while (l != r) {
		mid = (l + r) / 2;
		if (sign(beta[mid] - v) > 0) r = mid;
		else l = mid + 1;
	}
	e2[tot2] = l;
}

int main() {
	int T, Case, i, j, k;
	int numu, numd;
	LL ans, sum, sum1;

	vx.x = 1;
	vx.y = 0;
	
	//預處理出1-MAXN的逆元  快速冪
	for (i = 1; i < MAXN; i++)
		C[i] = Power(i, MOD - 2);

	cin >> T;
	for (Case = 1; Case <= T; Case++) {
		cin >> n;
		for (i = 0; i < n; i++)
			scanf("%lf%lf", &node[i].x, &node[i].y);
		ans = 0;
		//1 2 3 4個點都滿足條件,故(1,n) (2,n) (3,n) (4,n)必然存在
		for (i = 1; i <= 4; i++) {
			ans += c(n, i);
			ans %= MOD;
		}
		//列舉每一個點i   
		for (i = 0; i < n; i++) {
			tot = 0; tot2 = 0;
			//求出所有點對i點的極角  
			for (j = 0; j < n; j++) if (j != i)
				beta[tot++] = Angle(node[j] - node[i], vx);
			//按極角排序  
			qsort(beta, tot, sizeof(double), comp);
			
			//s1 s2真實存在 e1 e2不存在

			for (j = 0; j < tot; j++) {
				if (beta[j] < 0 || sign(PI - beta[j]) == 0) continue;
				if (sign(beta[j]) >= 0) {
					
					//s1表示 beta[j] 開始的位置;e1表示 beta[j] 結束的位置  
                    //s2表示 PI-beta[j] 即beta[j]的反方向;開始的位置 e2同理 
                    
					s1[tot2] = j;
					while (j + 1 != tot && sign(beta[j + 1] - beta[j]) == 0) j++;
					e1[tot2] = j + 1;
					len = e1[tot2] - s1[tot2];
					
					//Find用於二分查詢s2 e2的位置
					if (sign(beta[j]) == 0) Find(PI);
					else Find(beta[j] - PI);

					if (len >= 2) {
						//3 + 2
						
						if (sign(beta[j]) == 0) {
							numu = s2[tot2] - e1[tot2];//numu表示直線上方點的個數 
							numd = s1[tot2];//numd表示直線下方點的個數
						} else {
							numu = s1[tot2] - e2[tot2];
							numd = s2[tot2] + tot - e1[tot2];
						}
						
						 //上方下方都有點才會出現 3+2 
						if (numu != 0 && numd != 0) {
							sum = c(len, 2);
							sum %= MOD;
							sum1 = sum;
							for (k = 3; k <= len; k++) {
								sum *= len - k + 1;
								sum %= MOD;
								sum *= C[k];
								sum %= MOD;
								sum1 += sum;
								sum1 %= MOD;
							}
							sum1 *= (LL)numu*(LL)numd;
							sum1 %= MOD;
							ans += sum1;
							ans %= MOD;
						}

						if (len >= 3) {
							//4 + 1  
                            //上方或者下方有點才會出現4+1
							if (numu != 0 || numd != 0) {
								sum = c(len, 3);
								sum %= MOD;
								sum1 = sum;
								for (k = 4; k <= len; k++) {
									sum *= len - k + 1;
									sum %= MOD;
									sum *= C[k];
									sum %= MOD;
									sum1 += sum;
									sum1 %= MOD;
								}
								sum1 *= (LL)numu + (LL)numd;
								sum1 %= MOD;
								ans += sum1;
								ans %= MOD;
							}

							if (len >= 4) {
								//5
								sum = c(len, 4);
								sum %= MOD;
								sum1 = sum;
								for (k = 5; k <= len; k++) {
									sum *= len - k + 1;
									sum %= MOD;
									sum *= C[k];
									sum %= MOD;
									sum1 += sum;
									sum1 %= MOD;
								}
								ans += sum1;
								ans %= MOD;
							}
						}
					}
					if (e2[tot2] - s2[tot2] != 0) tot2++;//如果反方向也有點才記錄直線
				}
			}
			
			//列舉以i為中心兩兩直線,減去重複計算的次數
			for (j = 0; j < tot2; j++)
				for (k = j + 1; k < tot2; k++) {
					//四個方向的點的數量相乘
					sum = 1;
					sum *= e2[k] - s2[k];
					sum %= MOD;
					sum *= e2[j] - s2[j];
					sum %= MOD;
					sum *= e1[k] - s1[k];
					sum %= MOD;
					sum *= e1[j] - s1[j];
					sum %= MOD;
					ans = (ans + MOD - sum) % MOD;
				}
		}
		cout << "Case #" << Case << ": " << ans << endl;
	}
	return 0;
}