题解 - 【关于凸包的切点 + 环形区间覆盖】Rikka with Illuminations 2018-2019 ACM-ICPC, Asia Xuzhou Regional Contest M

题解 - 凸包切线 + 环形区间覆盖 2018-2019 ACM-ICPC, Asia Xuzhou Regional Contest M Rikka with Illuminations

题意:

Rikka with Illuminations

给你一个凸n边形,和m个灯(点)。问至少打开哪些灯才能照亮整个凸包?

保证灯在凸包外且不会和凸多边形的某边三点共线。 n , m ≤ 1000 n,m\le1000 n,m1000

银牌几何题。

分析:

首先,画一下某个灯“照亮”的范围,发现被照亮的区域等价于该灯与凸包的两条切线所夹的那部分区域。

于是对于每个点找到其关于凸包的两个切点。然后问题就变为了一个给你一个环和m个区间 ( l , r ) (l,r) (l,r),至少选哪几个区间可以覆盖整个圆。

挺直观的,后半部分代码能力有待提高。

实现:

  1. O ( n m ) O(nm) O(nm)求某点 p [ i ] p[i] p[i]关于凸包的切点:

    ​ 我们只要逆时针枚举凸包上的点 c h [ j ] ch[j] ch[j],计算now = (ch[j%n] - p[i]) | (ch[(j + 1)%n] - p[i]);。如果相邻的两个now符号不同,则说明那个点是切点。

    ​ 注意只算出两个切点是不够的,还要知道它代表环上哪边的区域。所以我们要确定起点和终点(不妨设逆时针绕)。画一下就能得出:

    				if (last >0  && now< 0) range[i].first = j%n;
    				if (last<0 && now>0) range[i].second = j%n;
    

    清华大学 24726 rsa:“可以用二分法优化到mlogn,然而很难写”

  2. O ( n 2 ) O(n^2) O(n2)环形区间覆盖:

    ​ 按套路处理环:拉直再翻倍。

    对应到每个区间 ( l , r ) (l,r) (l,r) 就是

    1. 如果 l &gt; r , r + = n l&gt;r,r+=n l>r,r+=n
    2. 对每个区间再增加一个 ( l + n , r + n ) (l+n,r+n) (l+n,r+n)的区间

    先枚举每个区间作为起点,然后贪心地做。维护当前区间的最右端点nowr,每次将l小于nowr的区间的右端点取个max,用来更新nowr。最后如果nowr>起点区间的l+n,用它更新答案。

    注意一个细节,若某次右端点取max的值小于nowr,则直接跳出循环。否则死循环了orz

    rep(i, 1, m) {
    			int nowr = trange[i].r;
    			int p = i + 1;
    			int f = 1;
    			vector<int >tmp;
    			tmp.push_back(trange[i].rk);
    			auto mx = Range(0, -1, 0);
    			while (nowr < trange[i].l + n) {
    				while (p<=2*m&&trange[p].l <= nowr)mx = max(mx, trange[p++]);
    				if(p >= 2 * m||mx.r<=nowr) {
    					f = 0; break;
    				}
    				nowr = mx.r;
    				tmp.push_back(mx.rk);
    			}
    			if (f == 0)continue;
    			if (tmp.size() < ans.size())ans = tmp;
    		}
    

    代码

    #include <iomanip>
    #include <cstring>
    #include <cstdlib>
    #include <cctype>
    #include <cstdio>
    #include <string>
    #include <stack>
    #include <cmath>
    #include <ctime>
    #include <list>
    #include <set>
    #include <map>
    #include <queue>
    #include <vector>
    #include <sstream>
    #include <memory>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
    #define per(i,j,k) for(int i = (int)j;i >= (int)k;i --)
    #define debug(x) cerr<<#x<<" = "<<(x)<<endl
    #define mmm(a,b) memset(a,b,sizeof(a))
    #define pb push_back
    
    typedef double db;
    typedef long long ll;
    const int MAXN = (int)1e5 + 5;
    const int INF = (int)0x3f3f3f3f;
    const int maxn = 1e4 + 5;
    int n, m;
    const double eps = 1e-7;
    struct V {
    	ll x, y;
    	V() {}
    	void sc() { scanf("%lld%lld", &x, &y); }
    	V(ll a, ll b) : x(a), y(b) { }
    	V operator+(V o) { return V(x + o.x, y + o.y); }
    	V operator-(V o) { return V(x - o.x, y - o.y); }
    	ll operator|(V o) { return x * o.y - o.x * y; }
    	void pr() { printf("%lld %lld\n", x, y); }
    } p[maxn];
    vector<V> ch;
    int out(int x) { return x ? x - 1 : ch.size() - 1; }
    int in(int x) { return x + 1 == (int)ch.size() ? 0 : x + 1; }
    struct Range{
    	int l;
    	int r;
    	int rk;
    	Range(int l=0, int r=0, int rk=0) :l(l), r(r), rk(rk) {}
    	bool operator < (const Range & b)const {
    		return r < b.r;
    	}
    }trange[maxn*2];
    pair<int, int> range[maxn];
    vector<int> ans;
    bool cmp(Range a, Range b) {
    	return a.l < b.l;
    }
    int main()
    {
    	int t; cin >> t;
    	while (t--) {
    		ch.clear();
    		cin >> n >> m;
    		rep(i, 1, n) {
    			V tmp;
    			cin >> tmp.x >> tmp.y;
    			ch.push_back(tmp);
    		}
    		rep(i, 1, m) p[i].sc();
    	
    		rep(i, 1, m) {
    			int j = 0;
    			db last = (ch[j] - p[i]) | (ch[j + 1] - p[i]);
    			rep(j, 1, n) {
    				db now = (ch[j%n] - p[i]) | (ch[(j + 1)%n] - p[i]);
    				//if (sgn(now) == 0)continue;
    				if (last >0  && now< 0) range[i].first = j%n;
    				if (last<0 && now>0) range[i].second = j%n;
    				last = now;
    			}
    		}
    		rep(i, 1, m) {
    			//cout << range[i].first << ' ' << range[i].second << endl;
    			//pt[range[i]] = i;
    			trange[i].l = range[i].first;
    			trange[i].r = range[i].second;
    			if (trange[i].r < trange[i].l)trange[i].r += n;
    			trange[i].rk = i;
    		}
    		rep(i, 1, m) {
    			trange[m + i] = Range(trange[i].l + n, trange[i].r + n, trange[i].rk);
    		}
    		sort(trange + 1, trange + 1 + 2*m,cmp);
    		ans.clear(); ans.resize(m+1);
    		rep(i, 1, m) {
    			int nowr = trange[i].r;
    			int p = i + 1;
    			int f = 1;
    			vector<int >tmp;
    			tmp.push_back(trange[i].rk);
    			auto mx = Range(0, -1, 0);
    			while (nowr < trange[i].l + n) {
    				while (p<=2*m&&trange[p].l <= nowr)mx = max(mx, trange[p++]);
    				if(p >= 2 * m||mx.r<=nowr) {
    					f = 0; break;
    				}
    				nowr = mx.r;
    				tmp.push_back(mx.rk);
    			}
    			if (f == 0)continue;
    			if (tmp.size() < ans.size())ans = tmp;
    		}
    		if (ans.size() == m + 1)cout << -1 << endl;
    		else {
    			cout << ans.size() << endl;
    
    			rep(j, 0, ans.size() - 1)cout << ans[j] << (j == ans.size() - 1 ? '\n' : ' ');
    			//for (auto t : ans)cout << t << ' ';
    			//cout << endl;
    		}
    		
    	}
    
    	cin >> t;
    }
    /*
    
    
    */
    
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Best KeyBoard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值