题目
题意概要
给出
n
n
n 个点,每个点向南偏东
3
0
∘
30^{\circ}
30∘ 或南偏西
3
0
∘
30^{\circ}
30∘ 引射线,使得围出的有限区域数量最多。区域的面积非零。
数据范围与约定
n
⩽
2000
n\leqslant 2000
n⩽2000,尽管这不是极限。
思路
具体哪两个角度并不重要,只要对于所有点都一视同仁,就等价于 “往左或往下”。
很难 d p \tt dp dp 的时候,找找方案的性质。为了找到性质,先看清题目的本质。
对于一个连通块,可以直接用欧拉公式 F = E − V + 2 F=E-V+2 F=E−V+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 F−1=I−∣S∣+1 为其贡献。对所有 S S S 求和,就得到了答案 I a l l − n + k I_{all}-n+k Iall−n+k,其中 k k k 为连通块数量。
插叙:若某个交点与射线的端点重合,则它只让 E E E 增加 1 1 1,贡献恰好还是 I − ∣ S ∣ + 1 I-|S|+1 I−∣S∣+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;
}