传送门 Codeforces 1860F Evaluate RBS
考虑 a x + b y − z = 0 ax+by-z=0 ax+by−z=0,观察到仅当两个平面的交线的两侧,次序交换。更简单地,将 a x + b y ax+by ax+by 看作 ( a , b ) , ( x , y ) (a,b),(x,y) (a,b),(x,y) 的点积,那么 ( a i , b i ) , ( a j , b j ) (a_i,b_i),(a_j,b_j) (ai,bi),(aj,bj) 次序交换仅出现在 ( a i − a j , b i − b j ) (a_i-a_j,b_i-b_j) (ai−aj,bi−bj) 的垂线方向 p p p。枚举 O ( n 2 ) O(n^2) O(n2) 点对,可以得到所有可能出现点次序交换的位置 p p p。将这些位置逆时针排序,依次处理,每次次序发生交换的点一定是位于数个连续区间,那么把这些可能交换的点单独进行排序,每个点对均摊处理 O ( 1 ) O(1) O(1),总时间复杂度 O ( n 2 log n ) O(n^2\log n) O(n2logn)。
在 p p p 两侧,点对的次序固定;恰好 ( x , y ) = p (x,y)=p (x,y)=p 时,数个点的点积相同,此时左括号置于前面(根据括号的性质,所有前缀和非负,则满足条件)。具体实现上,对于相邻位置 p i , p i + 1 p_i,p_{i+1} pi,pi+1 的中间的 ( x , y ) (x,y) (x,y),取 p i + p i + 1 p_i+p_{i+1} pi+pi+1 进行排序。
#include
using namespace std;
template <typename T>
struct Point {
T x, y;
bool operator<(const Point &o) const {
return det(o) > 0;
}
Point operator+(Point o) {
return {x + o.x, y + o.y};
}
T dot(Point o) {
return x * o.x + y * o.y;
}
T det(const Point &o) const {
return x * o.y - o.x * y;
}
};
using P = Point<long long>;
struct Value {
P v;
int c;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tt;
cin >> tt;
while (tt--) {
int n;
cin >> n;
vector<Value> val(2 * n);
for (int i = 0; i < 2 * n; ++i) {
char op;
auto &v = val[i].v;
cin >> v.x >> v.y >> op;
val[i].c = op == '(' ? 1 : -1;
}
map<P, vector<int>> pos;
for (int i = 0; i < 2 * n; ++i) {
for (int j = 0; j < i; ++j) {
auto dx = val[i].v.x - val[j].v.x;
auto dy = val[j].v.y - val[i].v.y;
if (dx < 0) {
dx *= -1;
dy *= -1;
}
if (dx <= 0 || dy <= 0) {
continue;
}
pos[{dy, dx}].push_back(i);
pos[{dy, dx}].push_back(j);
}
}
for (auto &[_, p] : pos) {
sort(p.begin(), p.end());
p.erase(unique(p.begin(), p.end()), p.end());
}
const int inf = 1e8;
P v{inf, 1};
vector<int> ord(2 * n), rnk(2 * n);
iota(ord.begin(), ord.end(), 0);
iota(rnk.begin(), rnk.end(), 0);
vector<int> pre(2 * n);
iota(pre.begin(), pre.end(), 0);
vector<int> sum(2 * n + 1);
int neg = 0;
int found = 0;
auto update = [&](P p, vector<int> &pos) {
int m = pos.size();
vector<int> tmp(m);
for (int i = 0; i < m; ++i) {
tmp[i] = rnk[pos[i]];
}
sort(pos.begin(), pos.end(), [&](int i, int j) {
auto x = p.dot(val[i].v), y = p.dot(val[j].v);
if (x != y) {
return x < y;
}
return val[i].c > val[j].c;
});
sort(tmp.begin(), tmp.end());
for (int i = 0; i < m; ++i) {
ord[tmp[i]] = pos[i];
rnk[pos[i]] = tmp[i];
neg -= sum[tmp[i] + 1] < 0;
sum[tmp[i] + 1] = sum[tmp[i]] + val[pos[i]].c;
neg += sum[tmp[i] + 1] < 0;
}
if (neg == 0) {
found = 1;
}
};
update(v, pre);
pre.clear();
for (auto &[u, cur] : pos) {
if (found) {
break;
}
P w = v + u;
update(w, pre);
update(u, cur);
pre = cur;
v = u;
}
cout << (found ? "YES" : "NO") << '\n';
}
return 0;
}
与上述计算几何方法原理相同,将 a x + b y ax+by ax+by 化作 a + b x / y a+bx/y a+bx/y,直观上即二维平面的直线随着 t = x / y t=x/y t=x/y 递增不断交换次序。 t = ( a i − a j ) / ( b j − b i ) t=(a_i-a_j)/(b_j-b_i) t=(ai−aj)/(bj−bi) 时,点 i , j i,j i,j 次序发生交换。预处理出 t → 0 + t\rightarrow 0^{+} t→0+ 情况下的次序,不断维护相邻点发生交换的 t t t 值,依次交换即可。总时间复杂度 O ( n 2 log n ) O(n^2\log n) O(n2logn)。
#include
using namespace std;
struct Value {
int a, b, c;
bool operator<(const Value& o) const {
if (a != o.a) {
return a < o.a;
}
if (b != o.b) {
return b < o.b;
}
return c > o.c;
}
};
struct Point {
long long x, y;
int d, p;
bool operator<(const Point& o) const {
auto t = y * o.x - o.y * x;
if (t != 0) {
return t < 0;
}
if (d != o.d) {
return d > o.d;
}
return p < o.p;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tt;
cin >> tt;
while (tt--) {
int n;
cin >> n;
n *= 2;
vector<Value> val(n);
for (int i = 0; i < n; ++i) {
char op;
cin >> val[i].a >> val[i].b >> op;
val[i].c = op == '(' ? 1 : -1;
}
sort(val.begin(), val.end());
vector<int> sum(n + 1);
int neg = 0;
for (int i = 0; i < n; ++i) {
sum[i + 1] = sum[i] + val[i].c;
neg += sum[i + 1] < 0;
}
set<Point> st;
auto get = [&](int i) -> Point {
return {val[i].b - val[i + 1].b, val[i + 1].a - val[i].a, val[i + 1].c - val[i].c, i};
};
auto op = [&](int i, bool add) {
if (0 <= i && i + 1 < n && val[i].b - val[i + 1].b > 0 && val[i + 1].a - val[i].a > 0) {
if (add) {
st.insert(get(i));
} else {
st.erase(get(i));
}
}
};
for (int i = 0; i + 1 < n; ++i) {
op(i, true);
}
auto eq = [&](const Point& a, const Point& b) {
return a.x * b.y - b.x * a.y == 0 && a.d == b.d;
};
auto judge = [&]() {
if (neg == 0) {
return true;
}
while (!st.empty()) {
auto v = *st.begin();
while (!st.empty() && eq(*st.begin(), v)) {
auto u = *st.begin();
st.erase(u);
int i = u.p;
op(i - 1, false);
op(i + 1, false);
neg -= sum[i + 1] < 0;
swap(val[i], val[i + 1]);
sum[i + 1] = sum[i] + val[i].c;
neg += sum[i + 1] < 0;
op(i - 1, true);
op(i, true);
op(i + 1, true);
}
if (neg == 0) {
return true;
}
}
return false;
};
cout << (judge() ? "YES" : "NO") << '\n';
}
return 0;
}