https://www.luogu.org/problem/show?pid=2831
BFS
看到N这么小就可以想到搜索,求最少步数显然应该用BFS。
在这题中过两猪可以唯一确定一条抛物线,每一步可以发射两只猪确定的一条抛物线(打下这条抛物线上的所有猪),也可以发射一条只经过一只猪的抛物线(只打下这只猪)。
这时可以想到状压存储每个状态,并且读入所有猪时预处理一下每两只猪确定的抛物线。
两点确定一条过原点的抛物线y=ax2+bx的方法:点(x1, y1) (x2, y2)过抛物线,得y1=ax12+bx1,y2=ax22+bx2,两式分别变形得y1/x1-ax1=y2/x2-ax2=b,再整理得a=(y2/x2-y1/x1)/(x2-x1)。求出a后往回代可求出b。
记得记录每个状态是否已经搜索过,避免重复的状态入队。由于共有2n个状态,故时间复杂度是O(2n)的。如果不剪枝就是O(n!)。
题目给的m应该是用来xjb剪枝用的,但是m=2怎么用我也不懂……
注意事项:
- 判断两个浮点数相等要考虑精度误差。
- stl的queue不开优化会很慢,可以考虑手写队列。
#include <algorithm> #include <cstring> #include <iostream> #include <queue> #include <vector> #include <cmath> using namespace std; bool equal(double x, double y, double eps = 1e-6) { return fabs(x - y) <= eps; } int n; bool m[3]; pair<double, double> pigs[20]; unsigned target; // 搜索的最终目标,即第1~n位均为1 unsigned pwxs[20][20]; // pwxs[i][j] => i、j两只猪所在抛物线上的所有猪 bool visited[(1 << 20)]; int bfs() { typedef pair<unsigned, int> state; // <状态, 步数> queue<state> q; q.push(make_pair(0, 0)); while (!q.empty()) { state x = q.front(); q.pop(); // 用一只鸟打一只猪的情况 for (int i = 1; i <= n; i++) { if (!(x.first & (1 << i))) // 如果i猪还没被打下 { state y = x; y.first |= (1 << i); y.second++; if (y.first == target) return y.second; if (!visited[y.first] && !(m[1] && y.second > (int)(n * 1.0 / 3 + 1))) { q.push(y); visited[y.first] = true; } } } // 用一只鸟打两只猪的情况 for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { if (!(x.first & (1 << i)) && !(x.first & (1 << j))) // 如果i、j猪都还没被打下 { state y = x; y.first |= pwxs[i][j]; y.second++; if (y.first == target) return y.second; if (!visited[y.first] && !(m[1] && y.second > (int)(n * 1.0 / 3 + 1))) { q.push(y); visited[y.first] = true; } } } } } return -1; } int main() { ios::sync_with_stdio(false); int t; cin >> t; while (t--) { memset(visited, false, 1 << 20); int c; cin >> n >> c; m[c] = true; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) pwxs[i][j] = 0; target = 0; for (int i = 1; i <= n; i++) target |= (1 << i); double a, b; for (int i = 1; i <= n; i++) { cin >> a >> b; pigs[i] = make_pair(a, b); } for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { /* ∵ y1=ax1^2+bx1, y2=ax2^2+bx2 ∴ b=y1/x1-ax1=y2/x2-ax2 ∴ a=(y2/x2-y1/x1)/(x2-x1) */ double &x1 = pigs[i].first, &y1 = pigs[i].second; double &x2 = pigs[j].first, &y2 = pigs[j].second; a = (y2 / x2 - y1 / x1) / (x2 - x1); b = y1 / x1 - a * x1; if (a < 0) { for (int k = 1; k <= n; k++) { double &x = pigs[k].first, &y = pigs[k].second; if (equal(a * x * x + b * x, y)) pwxs[i][j] |= (1 << k); } } } } cout << bfs() << endl; } return 0; }
看到N这么小就可以想到搜索,求最少步数显然应该用BFS。
在这题中过两猪可以唯一确定一条抛物线,每一步可以发射两只猪确定的一条抛物线(打下这条抛物线上的所有猪),也可以发射一条只经过一只猪的抛物线(只打下这只猪)。
这时可以想到状压存储每个状态,并且读入所有猪时预处理一下每两只猪确定的抛物线。
记得记录每个状态是否已经搜索过,避免重复的状态入队。