2 条题解
-
0
题解
思路分析
这是一道结合状态压缩DP和枚举抛物线的经典问题。
问题建模
- 给定平面上 个点(猪的位置)
- 每只小鸟的轨迹是形如 的抛物线()
- 求最少需要多少只小鸟消灭所有猪
核心思路
1. 抛物线的确定
两个不同的点可以唯一确定一条过原点的抛物线 。
给定点 和 ,可以解方程组:
$$\begin{cases} y_1 = ax_1^2 + bx_1 \\ y_2 = ax_2^2 + bx_2 \end{cases}$$解得:
$$a = \frac{x_2y_1 - x_1y_2}{x_1x_2(x_1-x_2)}, \quad b = \frac{x_1^2y_2 - x_2^2y_1}{x_1x_2(x_1-x_2)} $$需要满足 (抛物线开口向下)。
2. 预处理所有可能的抛物线
用二进制状态
line[i][j]表示过点 和点 的抛物线能覆盖哪些猪(用状压表示)。对于每对点 :
- 计算出 和
- 检查所有其他点是否在这条抛物线上(考虑浮点误差)
- 用二进制数记录覆盖的猪的集合
3. 状态压缩DP
定义 表示消灭状态 中的所有猪所需的最少小鸟数。
状态转移:
- 对于当前状态 ,找到第一个还未被消灭的猪
- 枚举所有经过猪 的抛物线(可能过单个猪或两个猪)
- 对于每条抛物线覆盖的猪的集合 :
其中 表示从状态 中去掉 覆盖的猪。
4. 优化技巧
- 枚举顺序优化:每次找到状态中最低位的 (第一个未消灭的猪),只枚举经过它的抛物线
- 单猪抛物线:即使一条抛物线只过一个猪,也要考虑(对应只消灭一只猪的情况)
算法步骤
-
预处理阶段:
- 枚举所有点对 ,计算抛物线参数
- 判断 是否满足
- 检查所有点,得到每条抛物线的覆盖集合
-
DP阶段:
- 初始化:,其余为
- 枚举状态 从 到
- 找到 中第一个为 的位(猪 )
- 枚举所有经过猪 的抛物线,更新
-
答案:
复杂度分析
- 预处理抛物线:(枚举点对 + 检查所有点)
- DP:
- 总时间复杂度:
- 空间复杂度:
由于 , 可以接受。
#include<bits/stdc++.h> using namespace std; int n, ans; double A[20], B[20], X[20], Y[20]; bool eq(double a, double b) { return abs(a - b) < 1e-8; } void dfs(int id, int s) { int sum = s; for (int i = 1;i < id;i++) { if (eq(A[i], 0)) { sum++; } } if (sum >= ans) { return; } if (id > n) { ans = sum; return; } for (int i = 1;i < id;i++) { if (!eq(A[i], 0.0) && eq(A[i] * X[id] * X[id] + B[i] * X[id], Y[id])) { A[id] = A[i]; B[id] = B[i]; dfs(id + 1, s); return; } if (A[i] == 0.0) { double d = X[i] * X[id] * (X[i] - X[id]); if (!eq(d, 0)) { double k1 = (X[id] * Y[i] - X[i] * Y[id]) / d, k2 = (X[i] * X[i] * Y[id] - X[id] * X[id] * Y[i]) / d; if (k1 < 0) { A[i] = A[id] = k1; B[i] = B[id] = k2; dfs(id + 1, s + 1); A[i] = B[i] = 0.0; } } } } A[id] = B[id] = 0.0; dfs(id + 1, s); } int main() { int t; cin >> t; while (t--) { int u; cin >> n >> u; memset(A, 0, sizeof(A)); memset(B, 0, sizeof(B)); memset(X, 0, sizeof(X)); memset(Y, 0, sizeof(Y)); for (int i = 1;i <= n;i++) { cin >> X[i] >> Y[i]; } ans = 1e9; dfs(1, 0); cout << ans << endl; } } -
0
题解
「愤怒的小鸟」要用尽量少的抛物线把所有的猪覆盖。
每条抛物线形如:并要求 (弹道必须向下开口)。思路是回溯枚举:
- 按输入顺序逐个处理点。数组 、 记录点 所在抛物线的系数, 表示尚未分配。
- 对当前点,若前面已存在抛物线且它能满足 则直接沿用该抛物线。
- 否则尝试与以前尚未建线的点两两配对。由两点坐标可以解出唯一的 ,只有当 时才是合法轨迹。临时把这两点分配到新抛物线并继续递归。
- 如果以上都不行,则把当前点单独留到后面(表示“这个点还没打”),继续递归。
统计当前已经新建的抛物线数量,若 (当前最优)就剪枝。
搜索结束时的最小 即为答案。由于 ,回溯结合剪枝就能通过。
#include<bits/stdc++.h> using namespace std; int n, ans; double A[20], B[20], X[20], Y[20]; bool eq(double a, double b) { return abs(a - b) < 1e-8; } void dfs(int id, int s) { int sum = s; for (int i = 1;i < id;i++) { if (eq(A[i], 0)) { sum++; } } if (sum >= ans) { return; } if (id > n) { ans = sum; return; } for (int i = 1;i < id;i++) { if (!eq(A[i], 0.0) && eq(A[i] * X[id] * X[id] + B[i] * X[id], Y[id])) { A[id] = A[i]; B[id] = B[i]; dfs(id + 1, s); return; } if (A[i] == 0.0) { double d = X[i] * X[id] * (X[i] - X[id]); if (!eq(d, 0)) { double k1 = (X[id] * Y[i] - X[i] * Y[id]) / d, k2 = (X[i] * X[i] * Y[id] - X[id] * X[id] * Y[i]) / d; if (k1 < 0) { A[i] = A[id] = k1; B[i] = B[id] = k2; dfs(id + 1, s + 1); A[i] = B[i] = 0.0; } } } } A[id] = B[id] = 0.0; dfs(id + 1, s); } int main() { int t; cin >> t; while (t--) { int u; cin >> n >> u; memset(A, 0, sizeof(A)); memset(B, 0, sizeof(B)); memset(X, 0, sizeof(X)); memset(Y, 0, sizeof(Y)); for (int i = 1;i <= n;i++) { cin >> X[i] >> Y[i]; } ans = 1e9; dfs(1, 0); cout << ans << endl; } }
- 1
信息
- ID
- 3238
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者