【2018徐州ICPC Gym-102012 M】Rikka with Illuminations【計算幾何】

題意:

給定一個 nn 個點的凸包,再給出 mm 個光照點,每個光照點的照射範圍爲 360360 度,問最少選取幾個光照點可以照亮整個凸包,要求輸出方案,保證不會出現一個光照點位於凸包的延長線上,共 200200 組數據。(1n,m1000)(1\leq n,m\leq 1000)


思路:

其實這題思路比較明顯,就是先求出每個光照點所能照射到的一段連續範圍,然後問題就變成了給定一個長度爲 nn 的環形區域,以及 mm 段區間,要求選取最少的區間覆蓋整個區域。

首先求每個光照點對應的一段連續區域,比賽時的思路是極角排序,然後選擇最兩邊的邊。這樣的複雜度是 O(n2logn)O(n^2logn),再加上 200200 組數據,成功 TLETLE…其實仔細思考一下可以發現並不需要進行極角排序,利用叉積即可解決該問題。因爲照射範圍的兩個端點一定是光照點與凸包點連線形成的直線中最兩邊的兩個點。

因此其它所有端點與光照點連線形成的直線都是順時針或逆時針才能到達兩個端點,即照射範圍的兩個端點是順時針旋轉的兩個邊界點,因此直接用叉積判斷即可。

for(int i = 0; i <= m-1; i++){
	L[i] = 0, R[i] = 0;
	rep(int j = 1; j <= n-1; j++){
		//利用叉積求切線
		if((P[L[i]]-H[i]).det(P[j]-H[i]) > 0) L[i] = j;
		if((P[R[i]]-H[i]).det(P[j]-H[i]) < 0) R[i] = j;
	}
}

求出每個光照點範圍之後,就只需要處理區間覆蓋問題了。環形區域,我們只需要枚舉起始點,O(n2)O(n^2) 求解即可。我們記錄 add[i]add[i] 數組表示所有覆蓋第 ii 條的光照點中所能覆蓋的最遠距離,id[i]id[i] 記錄光照點編號,然後直接貪心即可解決該問題。


總結:

計算幾何的絕大多數問題,都可以用叉積和點積進行解決,因爲叉積和點積已經覆蓋了兩條直線所能出現的所有組合情況。

因此今後的計算幾何問題,一定要首選用叉積和點積進行解決。


代碼:

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
#define pi acos(-1.0)
const int N = 1e3+100;
const db EPS = 1e-9;
using namespace std;

void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}
inline int sign(db a) {return a < -EPS ? -1 : a > EPS;}
inline int cmp(db a,db b) {return sign(a-b);}

struct Point{
	ll x,y;
	Point operator-(Point p) {return {x-p.x,y-p.y};}
	ll dot(Point p) {return x*p.x+y*p.y;}
	ll det(Point p) {return x*p.y-y*p.x;} //叉積
}P[N],H[N];

int n,m,L[N],R[N],add[N],id[N],ans;
vector<int> base;

void init(){
	scanf("%d%d",&n,&m);
	rep(i,0,n-1) scanf("%lld%lld",&P[i].x,&P[i].y);
	rep(i,0,m-1) scanf("%lld%lld",&H[i].x,&H[i].y);
	rep(i,0,n-1) add[i] = id[i] = 0;
	rep(i,0,m-1){
		L[i] = 0, R[i] = 0;
		rep(j,1,n-1){
			//利用叉積求切線
			if((P[L[i]]-H[i]).det(P[j]-H[i]) > 0) L[i] = j;
			if((P[R[i]]-H[i]).det(P[j]-H[i]) < 0) R[i] = j;
		}
		int len = (R[i]-L[i]+n)%n;
		rep(j,0,len-1){
			int now = (L[i]+j)%n;
			int tlen = (R[i]-now+n)%n;
			if(tlen > add[now]) add[now] = tlen, id[now] = i;
		}
	}
}

void solve(){
	ans = 1e5; base.clear();
	rep(i,0,n-1){
		int pos = i, num = 0, left = n;
		vector<int> hp; hp.clear();
		while(left > 0){
			if(add[pos] == 0) {num = 1e5; break;}
			left -= add[pos];
			hp.push_back(id[pos]);
			pos = (pos+add[pos])%n;
			num++;
		}
		if(num < ans){
			ans = num;
			base = hp;
		}
	}
	if(ans == 1e5) printf("-1\n");
	else{
		printf("%d\n",ans);
		rep(i,0,ans-1) printf("%d%c",base[i]+1," \n"[i==ans-1]);
	}
}

int main()
{
	int _; scanf("%d",&_);
	while(_--){
		init();
		solve();
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章