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的極角排序,這樣就能找出共線的點,這種情況下情況
這樣這個問題就完全解決了。對於更具體的實現,計算直線兩側的點的個數 以及 直線上兩邊點的個數,直接列舉大於等於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; }