Kiana最近沉迷于一款神奇的游戏无法自拔。

简单来说,这款游戏是在一个平面上进行的。

有一架弹弓位于 (0,0) (0, 0) 处,每次Kiana可以用它向第一象限发射一只小鸟,小鸟们的飞行轨迹均为形如 y=ax2+bx y = ax ^ 2 + bx 的曲线,其中 a a b b 是Kiana指定的参数,且必须满足 a<0 a < 0

当小鸟落回地面(即 x x 轴)时,它就会瞬间消失。

在游戏的某个关卡里,平面的第一象限中有 n n 只绿色的猪,其中第 i i 只猪所在的坐标为 (xi,yi) (x_i, y_i)

如果某只小鸟的飞行轨迹经过了(xi,yi) (x_i, y_i) ,那么第 i i 只猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;

如果一只小鸟的飞行轨迹没有经过(xi,yi) (x_i, y_i) ,那么这只小鸟飞行的全过程就不会对第 i i 只猪产生任何影响。

例如,若两只猪分别位于 (1,3) (1, 3) (3,3) (3, 3) ,Kiana可以选择发射一只飞行轨迹为 y=x2+4x y = -x ^ 2 + 4x 的小鸟,这样两只猪就会被这只小鸟一起消灭。

而这个游戏的目的,就是通过发射小鸟消灭所有的猪。

这款神奇游戏的每个关卡对来说都很难,所以Kiana还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。这些指令将在「输入格式」中详述。

假设这款游戏一共有 T T 个关卡,现在Kiana想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的猪。由于她不会算,所以希望由你告诉她。

链接

LYOI#104

题解

状压

因为猪的数量最多只有 18 18 只,所以自然而然地想到状压,这里说一下本题状压的思想。

我们用一个形如 011011001100110010 的二进制数,表示游戏中现存的猪,其中从左往右数第 i i 位,表示的是编号为 i i 的猪的状态,该位为 0 0 ,表示这只猪死亡或本身就不存在,反之表示这只猪现在还活着。

对于一条抛物线(真·题解中会提到抛物线相关),我们也用一个类似的二进制数记录它的信息,即它的从左往右第 i i 位为 1 1 ,表示编号为 i i 的猪可以被这条抛物线杀死,反之此抛物线不会对这只猪造成任何影响。

用位运算乱搞就可以表示杀猪了 QwQ.

真·题解

由于弹弓的位置确定,为 (0,0) (0,0) ,则可以由两个点唯一确定出一条形如题目描述中 ax2+by=0 a x ^ 2 + b y = 0 抛物线,枚举每两只猪的坐标,求出 n2 n^2 条抛物线,并舍去其中不合题意的抛物线(即 a>0 a > 0 的抛物线),使用一个二进制数来记录每条合法抛物线可击杀的猪。

可以证明,这样枚举一定最优,因为每条击杀猪数目超过 2 2 的抛物线,一定会击杀至少两只猪,而所有可至少击杀两只猪的合法抛物线都在枚举的过程中被求出,则如此枚举可以所有击杀猪的数量最多的抛物线。

但是这样还会有几只猪不在我们枚举到的任何一条合法的抛物线上,对于这种猪,我们为其虚拟出一条抛物线,我们不需要知道其方程,只需要标记这条抛物线可以击杀对应的猪即可。

然后建立状态图进行 BFS 即可。

当然在操作的过程中可能会用到各种各样的位运算操作,状压当中常见的奇技淫巧。

代码

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <climits>
#include <queue>
using namespace std;
const int MAXN = 18 + 3;
const int MAXS = (1 << (MAXN + 1));
const double EPS = 1e-6; // 精度搞的高一点
struct Point {
double x, y;
Point(double x = 0, double y = 0): x(x), y(y) {}
} a[MAXN];
inline bool dcmp (double x) {
return fabs(x) <= EPS;
}
inline bool dcmp (double x, double y) {
return dcmp(x - y);
} // 处理浮点数时常用的根据 EPS 判断相等的技巧
struct Line {
double a, b;
bool valid;
unsigned int kill;
Line(unsigned int kill = 0): a(0), b(0), valid(true), kill(kill) {}
bool operator<(const Line &other) const {
return kill > other.kill;
}
bool operator==(const Line &other) const {
return kill == other.kill;
}
} lines[MAXN * MAXN];
struct Node {
bool vis;
int dist;
} N[MAXS];
int n, cmd, limit, lineCnt;
inline int bfs (int status) {
if (!status) return 0;
queue<int> q;
q.push(status);
N[status].vis = true;
N[status].dist = 0;
while (!q.empty()) {
int v = q.front();
q.pop();
if (N[v].dist == limit) continue;
for (int i = 1; i <= lineCnt; i++) {
Line &l = lines[i];
if (l.valid && (l.kill & status)) {
int u = v;
u &= ~l.kill; // 状压通过位运算搞掉
if (!N[u].vis) {
N[u].vis = true;
N[u].dist = N[v].dist + 1;
if (!u) return N[u].dist;
q.push(u);
}
}
}
}
return n;
}
inline Line getLine (const Point &a, const Point &b) {
Line l;
l.a = (a.x * b.y - b.x * a.y) / (a.x * b.x * b.x - b.x * a.x * a.x);
l.b = (a.y - l.a * a.x * a.x) / a.x;
return l;
}
inline bool onLine (const Line &l, const Point &a) {
return dcmp(l.a * a.x * a.x + l.b * a.x, a.y);
}
inline int solve () {
bool flags[MAXN];
for (int i = 1; i <= n; i++) flags[i] = false;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (dcmp(a[i].x, a[j].x)) continue;
Line l = getLine(a[i], a[j]);
if (l.a >= 0) continue;
flags[i] = flags[j] = true;
lines[++lineCnt] = l;
}
}
for (int i = 1; i <= lineCnt; i++) {
for (int j = 1; j <= n; j++) {
if (onLine(lines[i], a[j])) {
lines[i].kill |= (1u << (j - 1));
}
}
}
for (int i = 1; i <= n; i++) {
if (!flags[i]) {
lines[++lineCnt] = Line(1u << (i - 1)); // 或运算标记能击杀的猪的编号
}
}
sort(lines + 1, lines + lineCnt + 1);
for (int i = 1; i <= lineCnt; i++) {
for (int j = i + 1; j <= lineCnt; j++) {
if ((lines[i].kill | lines[j].kill) == lines[i].kill) {
lines[j].valid = false;
} // 如果抛物线i能击杀抛物线j能击杀的所有猪,而且能击杀抛物线j不能击杀的猪,则使用抛物线j明显不优
}
}
for (unsigned int i = 0; i < (1u << n); i++) {
N[i].vis = false;
}
return bfs((1u << n) - 1);
}
int main() {
freopen("angrybirds.in", "r", stdin);
freopen("angrybirds.out", "w", stdout);
int t;
scanf("%d", &t);
while (t--) {
scanf("%d %d", &n, &cmd);
for (int i = 1; i <= n; i++) {
scanf("%lf %lf", &a[i].x, &a[i].y);
}
lineCnt = 0;
if (cmd == 1) {
limit = ceil(double(n) / 3 + 1);
} else {
limit = n;
}
printf("%d\n", solve());
}
fclose(stdin);
fclose(stdout);
}

最后写完这题才发现,貌似那个作弊指令没有什么卵用