题解:P12767 [POI 2018 R3] 围栏 Fence

· · 题解

有点厉害的题。也可能是我没见过这类套路。

预处理三角形内点权和与 dp 转移都是 $\mathcal{O}(n^3)$ 的,需要进一步优化。 ### Part I:三角形内数点 假设我们考察的是 $i<j$ 与原点构成的三角形。对于一个点 $k$,他必须要满足 $i<k<j$ 才可能被计入贡献。 这看着怎么这么像一维限制,那是不是得想办法凑出第二维?三角形数点我不会,但是二维数点我会啊! 发现 $i<k<j$ 本质上是限制了夹角的范围。考虑用两个角就能夹出来一个三角形,于是我们将 $i+1\sim n$ 的点以 $i$ 为中心极角排序,得到每个点的排名 $rk$,那么 $k$ 能造成贡献的的条件为 $rk_k<rk_j$。这样就是二维数点了,上树状数组即可,时间复杂度 $\mathcal{O}(n^2\log n)$。实测只做这个优化也[跑的飞快](https://www.luogu.com.cn/record/258616531)。 ### Part II:dp 转移 朴素的转移就是设 $dp_{i,j}$ 表示上一条边为 $i\to j$ 时的答案,转移需要枚举上一个点 $k$ 并判断是否能构成凸包,然后 $dp_{i,j}\gets dp_{k,i}+w(i,j)$。 也就是我们要做的是快速求 $\max_k dp_{k,i}$。注意到极角排序后 $k$ 构成一段前缀,前缀和优化 dp 即可。时间复杂度 $\mathcal{O}(n^2\log n)$。 于是我们在 $\mathcal{O}(n^3\log n)$ 的复杂度下解决了这个问题。 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 3e2 + 10; typedef struct Point { ll x, y; Point(ll x = 0, ll y = 0) : x(x), y(y) {} Point operator - (const Point &rhs) const { return Point(x - rhs.x, y - rhs.y); } ll operator ^ (const Point &rhs) const { return x * rhs.y - y * rhs.x; } } Vector; int n, m; pair<Point, ll> a[MAXN], b[MAXN]; ll w[MAXN][MAXN], dp[MAXN][MAXN], ans = -1e18; int id[MAXN], rk[MAXN]; ll c[MAXN]; inline void add(int k, ll x) { for (int i = k; i <= m; i += i & -i) c[i] += x; } inline ll ask(int k) { ll res = 0; for (int i = k; i; i &= i - 1) res += c[i]; return res; } inline void solve() { sort(b + 1, b + m + 1, [](auto x, auto y) -> bool { if (!x.first.x && !x.first.y) return 1; return (x.first ^ y.first) > 0; }); for (int i = 2; i < m; i++) { for (int j = i + 1; j <= m; j++) id[j] = j; sort(id + i + 1, id + m + 1, [=](auto x, auto y) { return (b[x].first - b[i].first ^ b[y].first - b[i].first) < 0; }); for (int j = i + 1; j <= m; j++) rk[id[j]] = j; for (int j = 1; j <= m; j++) c[j] = 0; for (int j = i + 1; j <= m; j++) w[i][j] = b[i].second + ask(rk[j]), add(rk[j], b[j].second); } memset(dp, 0xc0, sizeof dp); for (int i = 2; i <= m; i++) dp[1][i] = b[1].second; for (int i = 2; i < m; i++) { vector<int> tmp; for (int j = 1; j <= m; j++) if (j != i) tmp.emplace_back(j); sort(tmp.begin(), tmp.end(), [=](int x, int y) -> bool { Vector tx = x < i ? b[i].first - b[x].first : b[x].first - b[i].first; Vector ty = y < i ? b[i].first - b[y].first : b[y].first - b[i].first; return (tx ^ ty) > 0; }); ll val = -1e18; for (int j : tmp) { if (j > i) dp[i][j] = max(dp[i][j], val + w[i][j]); else val = max(val, dp[j][i]); } } for (int i = 2; i <= m; i++) { for (int j = i + 1; j <= m; j++) ans = max(ans, dp[i][j] + b[j].second); } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%lld%lld%lld", &a[i].first.x, &a[i].first.y, &a[i].second); for (int i = 1; i <= n; i++) { m = 0; for (int j = 1; j <= n; j++) { if (a[j].first.y >= a[i].first.y) { b[++m] = make_pair(a[j].first - a[i].first, a[j].second); } } solve(); } printf("%lld", ans); } ```