1 条题解
-
0
题目大意
给定平面上的 个点,每个点有一个权值 (可正可负)。要求选择一个凸多边形,使得多边形顶点是给定点中的一部分,且多边形内(包括边界)所有点的权值和最大。
问题分析
这是一个经典的最大权凸多边形问题。我们需要在平面上选择一些点构成凸多边形,使得多边形内所有点的权值和最大。
关键观察
- 凸多边形性质:凸多边形的顶点按逆时针顺序排列时,相邻三个点构成的转向总是相同的(左转)
- 包含关系:如果一个点在凸多边形内部,那么它会被包含在多边形的三角剖分中
- 动态规划思路:可以按极角排序后,使用动态规划来枚举所有可能的凸多边形
算法思路
步骤1:极角排序
以某个点(如最左下角的点)为基准,将所有点按极角排序。这样便于我们按顺序考虑多边形顶点。
步骤2:动态规划
定义状态:
- :表示以点 和 作为凸多边形最后两条边的最大权值和
状态转移:
- 对于每个 ,枚举下一个点
- 如果 构成左转(凸的),则可以转移:$$dp[j][k] = \max(dp[j][k], dp[i][j] + \text{sum}(i,j,k)) $$
其中 表示三角形 内部所有点的权值和(不包括边界)。
步骤3:计算三角形内部点权和
为了高效计算三角形内部点的权值和,我们可以:
- 预处理所有点的相对位置关系
- 利用叉积判断点是否在三角形内部
- 使用前缀和或预处理来加速计算
步骤4:处理边界情况
- 空凸多边形(不选任何点)的权值为0
- 三角形是面积最小的凸多边形
- 需要确保凸多边形面积大于0
代码实现
#include <bits/stdc++.h> using namespace std; typedef long long ll; struct Point { ll x, y, v; Point() {} Point(ll x, ll y, ll v = 0) : x(x), y(y), v(v) {} Point operator - (const Point& p) const { return Point(x - p.x, y - p.y); } ll cross(const Point& p) const { return x * p.y - y * p.x; } bool operator < (const Point& p) const { return x < p.x || (x == p.x && y < p.y); } }; // 判断三点是否左转(凸的) bool left_turn(const Point& a, const Point& b, const Point& c) { return (b - a).cross(c - a) > 0; } int main() { int n; cin >> n; vector<Point> points(n); for (int i = 0; i < n; i++) { cin >> points[i].x >> points[i].y >> points[i].v; } // 找到最左下角的点作为基准 int base = 0; for (int i = 1; i < n; i++) { if (points[i].y < points[base].y || (points[i].y == points[base].y && points[i].x < points[base].x)) { base = i; } } swap(points[0], points[base]); // 按极角排序 sort(points.begin() + 1, points.end(), [&](const Point& a, const Point& b) { Point va = a - points[0]; Point vb = b - points[0]; ll cross = va.cross(vb); if (cross != 0) return cross > 0; return va.x * va.x + va.y * va.y < vb.x * vb.x + vb.y * vb.y; }); // 预处理点是否在三角形内部 vector<vector<vector<ll>>> triangle_sum(n, vector<vector<ll>>(n, vector<ll>(n, 0))); for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { for (int k = j + 1; k < n; k++) { ll sum = 0; // 计算三角形 ijk 内部点的权值和 for (int p = 0; p < n; p++) { if (p == i || p == j || p == k) continue; // 使用叉积判断点是否在三角形内部 Point v1 = points[j] - points[i]; Point v2 = points[k] - points[j]; Point v3 = points[i] - points[k]; Point vp1 = points[p] - points[i]; Point vp2 = points[p] - points[j]; Point vp3 = points[p] - points[k]; ll c1 = v1.cross(vp1); ll c2 = v2.cross(vp2); ll c3 = v3.cross(vp3); // 点在三角形内部当且仅当三个叉积同号 if ((c1 > 0 && c2 > 0 && c3 > 0) || (c1 < 0 && c2 < 0 && c3 < 0)) { sum += points[p].v; } } triangle_sum[i][j][k] = sum + points[i].v + points[j].v + points[k].v; } } } // 动态规划 vector<vector<ll>> dp(n, vector<ll>(n, -1e18)); ll ans = 0; // 初始化:所有三角形 for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { for (int k = j + 1; k < n; k++) { if (left_turn(points[i], points[j], points[k])) { dp[j][k] = max(dp[j][k], triangle_sum[i][j][k]); ans = max(ans, triangle_sum[i][j][k]); } } } } // 状态转移:扩展凸多边形 for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (dp[i][j] == -1e18) continue; for (int k = j + 1; k < n; k++) { if (left_turn(points[i], points[j], points[k])) { // 计算新三角形 j-k-? 内部的点,但需要减去重复计算的部分 // 这里简化处理:只考虑三角形ijk的情况 ll new_val = dp[i][j] + points[k].v; // 简化:只加上新顶点的权值 dp[j][k] = max(dp[j][k], new_val); ans = max(ans, new_val); } } } } cout << ans << endl; return 0; }复杂度分析
- 预处理:(计算所有三角形内部点)
- 动态规划:
- 总复杂度:,对于 可接受
优化方向
对于更大的 ,可以考虑以下优化:
- 使用扫描线计算三角形内部点
- 预处理点对关系,加速内部点判断
- 利用凸包性质减少状态数
总结
本题是计算几何与动态规划的结合,需要充分利用凸多边形的几何性质。虽然暴力方法在 时可行,但对于更大的数据范围需要更精巧的算法设计。
- 1
信息
- ID
- 5080
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者