[ACNOI2022]我所不能

89 篇文章 0 订阅
本文探讨了一个涉及几何与优化的问题:给定2000个点,通过调整射线方向使其形成最多数量的非零面积连通区域。作者通过欧拉公式、贪心策略和二维偏序分析,最终提出一种O(n log n)时间复杂度的解决方案,仅需计算每个点的左右邻居数量。
摘要由CSDN通过智能技术生成

题目

题意概要
给出 n n n 个点,每个点向南偏东 3 0 ∘ 30^{\circ} 30 或南偏西 3 0 ∘ 30^{\circ} 30 引射线,使得围出的有限区域数量最多。区域的面积非零。

数据范围与约定
n ⩽ 2000 n\leqslant 2000 n2000,尽管这不是极限。

思路

具体哪两个角度并不重要,只要对于所有点都一视同仁,就等价于 “往左或往下”。

很难 d p \tt dp dp 的时候,找找方案的性质。为了找到性质,先看清题目的本质。

对于一个连通块,可以直接用欧拉公式 F = E − V + 2 F=E-V+2 F=EV+2 计算数量:射线相交时,加入一个交点会导致 E E E 增大 2 2 2 。记交点数为 I I I,则 E = 2 I E=2I E=2I 。而 V = ∣ S ∣ + I V=|S|+I V=S+I,其中 ∣ S ∣ |S| S 为当前连通块的大小(射线数量)。所以 F − 1 = I − ∣ S ∣ + 1 F-1=I-|S|+1 F1=IS+1 为其贡献。对所有 S S S 求和,就得到了答案 I a l l − n + k I_{all}-n+k Ialln+k,其中 k k k 为连通块数量。

插叙:若某个交点与射线的端点重合,则它只让 E E E 增加 1 1 1,贡献恰好还是 I − ∣ S ∣ + 1 I-|S|+1 IS+1 不变。还有个 corner case \text{corner case} corner case 是两条射线重叠,此时 I I I 有 “无穷个”。但这显然不是最优方案,根本就不要考虑了吧 😅

此时,考虑 贪心 来给出限制条件。我认为我是不可能看出来的——要同时调整两个射线的方向。若 a a a b b b 的左上方(非严格),则 a a a 向左、 b b b 向下(射线不相交)一定不优于 a a a 向下、 b b b 向左(射线相交)。证明需要一点点分析。

原本与 a a a 相交的,现在与 b b b 相交(毕竟 b b b 向左相比于 a a a 向左是更低更长的),所以仍与 a a a 连通。现在与 a a a 相交的,要么原本与 b b b 相交,这可以统一到 a , b a,b a,b 连通块合并;要么原本与 b b b 不相交,那么本次相交会提供 + 1 +1 +1 的交点数量变化,同时也可能带来 − 1 -1 1 的连通块数量变化,可以抵消。最后只剩 a , b a,b a,b 连通块合并,可以与 a , b a,b a,b 相交产生的交点贡献 + 1 +1 +1 相抵消。

b b b 上的分析同理。所以这样操作之后,答案的增加量是非负数,当然不劣。

现在仍感觉 d p \tt dp dp 困难啊……因为说到底,交点是个二维偏序,怎么记录 d p \tt dp dp 状态才能求数量啊 😂

最好继续贪心。此时终于可以只考虑一个射线的方向了:对于一个向左的射线 a a a,试试将其调整为向下。由上面的性质, a a a 右下方所有射线都朝左,所以 a a a 改为向下,新增的交点就是右下方点的数量。而减少的交点数量不超过左上方点的数量。故,右下角的点不小于左上角的点数量时,该点必然朝下。反之,该点必然朝左。分析同理。

天呐,我们直接得到了答案!只需要求出每个点左上方与右下方点的数量。这是一个简单的二维偏序,时间复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 。计算答案也是二维偏序,仍是 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

代码

这里就用 O ( n 2 ) \mathcal O(n^2) O(n2) 的暴力实现了。

#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG LONG for loneliness)
#include <cctype> // DDG yydDOG & ZXY yydBUS !!!
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MAXN = 2003;
double x[MAXN], y[MAXN];

int fa[MAXN];
inline int findSet(int a){
	if(fa[a] == a) return a;
	return fa[a] = findSet(fa[a]);
}

const double sqrt3 = 1.7320508075688772935274463415059;
bool bel[MAXN]; ///< if it chooses left
int main(){
	int n = readint();
	rep(i,1,n){
		int _x = readint(), _y = readint();
		x[i] = sqrt3*_x+_y, y[i] = _y-sqrt3*_x;
	}
	rep(i,1,n){
		int lu = 0, rd = 0;
		rep(j,1,n) if(j != i){
			if(x[j] <= x[i] && y[i] <= y[j]) ++ lu;
			if(x[i] <= x[j] && y[j] <= y[i]) ++ rd;
		}
		bel[i] = (lu > rd); // choose left
	}
	rep(i,1,n) fa[i] = i;
	int ans = 0; // n component
	rep(i,1,n) if(!bel[i]) rep(j,1,n) if(j != i)
		if(bel[j] && x[i] <= x[j] && y[j] <= y[i]){
			if(findSet(i) == findSet(j)) ++ ans;
			else fa[fa[i]] = fa[j]; // link
		}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值