CF1284E·New Year and Castle Construction

初見安~這裏是傳送門:Codeforces 1284 E

Sol

一眼計算幾何,然後就不可做了【啪。

題意是這樣的:平面上有n個點,問你存在多少組四個點圍成的四邊形【也可能是三角形】嚴格包圍某個點的情況。不存在三點共線。

【思路、代碼參考https://www.bilibili.com/video/av82161298?p=4

很容易發現,我們不管事考慮每個點有多少個四邊形包含還是每個四邊形包含多少個點,都是個比較難整的問題。【聽說可以用掃描線?忘了怎麼寫了。】所以我們不妨倒過來考慮:有多少種情況是不合法的,也就是考慮四邊形不能包含點的情況。選擇點的總數是\small C_n^5,這個不必多說。

題目數據範圍支持\small O(n^2logn)明顯在暗示要枚舉每個點。

我們這麼看:我們枚舉每個點, 找有多少個四邊形是不能包含它的

可以隨手畫一個看看,我們將這個點放在直角座標系的原點位置。

很明顯,如果另外四個點都在同一個象限,是肯定不會包含它的。那麼在同一個平面內呢?

很明顯也是滿足的,不會經過原點。如果四個點中有一個到三個點到了y的負半軸,那麼就很有可能會過原點了。

但是你可以說:如果四個點都在y的負半軸,那不是也滿足條件嗎?

所以我們判斷的條件可以完善爲:四個點與原點連線的最大夾角不超過180度,那麼這四個點連成的四邊形就不會經過原點

所謂最大夾角:

就是圖中的\small \angle \alpha。沒有超過180度,所以這四個點的連線明顯不會經過原點。

那麼怎麼找這樣的四個點呢?我們可以固定其中一個點,然後順時針延展180度以內,看看這中間有多少個點被包含進來,然後隨便選三個即可。所以題目保證了沒有三點共線就是非常好的一個條件,大大簡化了我們的過程。

最後梳理一下計算過程:枚舉每個點,找不包含這個點的四邊形,也就是處理出這個點以外的所有點的角度。我們可以用到一個函數叫做\small atan2(double (a), double(b))【注意裏面是浮點類型!!!】表示求出座標爲(a,b)的點和原點的座標夾角【注意是弧度制】。排個序,順時針掃每個點作爲最右端【或者說是弧度最小的那個點】的時候的情況,看看有多少個點可以包含進弧度π以內,隨便選三個即可。

是不是特別的巧妙!!!!!!!

但是這個題有點坑【有可能是計算幾何共有的坑】就是精度問題。你不開long double就一直WA9,還有求ans的時候很容易就爆int……

上代碼——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#define maxn 3000
using namespace std;
typedef long long ll;
const long double pai = acos(-1.0L);//注意1.0後面有個L,這個影響精度的很重要
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}
 
int n;
long double x[maxn], y[maxn];
vector<long double> v;
signed main() {
	n = read();
	for(int i = 0; i < n; i++) scanf("%Lf%Lf", &x[i], &y[i]);
	
	ll ans = 1ll * n * (n - 1) * (n - 2) * (n - 3) * (n - 4) / 24;//C_n^5,所有情況數 
	for(int i = 0; i < n; i++) {
		v.clear();
		for(int j = 0; j < n; j++) if(i != j) v.push_back(atan2(x[j] - x[i], y[j] - y[i]));//放進每個點的角度 
		sort(v.begin(), v.end());//排個序 
		register int now = 0;//用now線性掃描,可以降低複雜度的 
		ll cnt;
		for(int j = 0; j < n - 1; j++) {
			while(now < j + n - 1) {//因爲平面是環形的,所以用延長一倍的技巧 
				long double tmp = v[now % (n - 1)] - v[j];//這纔是這個點相對點j的實際角度 
				if(tmp < 0) tmp += 2 * pai;//多轉一圈好判定 
				if(tmp < pai) now++;
				else break;//大於π了明顯不合法了 
			}
			cnt = (now - j - 1);//這裏的邊界手枚一下就很好理解的 
			ans -= cnt * (cnt - 1) * (cnt - 2) / 6;//cnt一定要開Long long!!這很重要 
		}
	}
	printf("%lld\n", ans);
	return 0;
}

呼……這就算平面幾何入門了吧。就像數論隨手開long long一樣,要隨手開long double……

迎評:)
——End——

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章