You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

91 lines
5.4 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
using namespace std;
const int N = 18; // 小猪的数量上限
const int M = 1 << N; // 用二进制可以模拟出N只小猪的所有状态 0~2^N-1种状态
const double eps = 1e-8; // 浮点数的精度
const int INF = 0x3f3f3f3f; // 正无穷
// 结构体,小猪坐标
struct Node {
double x, y;
} pig[N];
int path[N][N]; // path[i][j]:第i只和第j只小猪构成的抛物线能覆盖的所有状态(是一个状态压缩值比如0101=5)
int f[M]; // f[i]表示覆盖掉目前i的状态表示中所有小猪最少需要多少条抛物线也就是最少需要多少只小鸟
int n; // n只小猪
int m; // Kiana 输入的神秘指令类型,此变量没有用到,据说可以用来骗分,没有仔细研究
int cmp(double a, double b) { // 浮点数比较
return abs(a - b) < eps;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
// 读入每只小猪的坐标位置
for (int i = 0; i < n; i++) scanf("%lf%lf", &pig[i].x, &pig[i].y); // 因为采用状压DP下标从0开始
// 1、预处理所有可能的抛物线记录抛物线与每个小猪的覆盖关系
memset(path, 0, sizeof path); // 多组数据,每次清空
/*通过分析知道 y=ax^2+bx (∵小鸟从圆点出发∴c=0)
如何确定一条抛物线呢其实就是确定下来a,b的值。
方法是通过枚举任意两个小猪:(x1,y1),(x2,y2)确定下来一条抛物线
*/
for (int i = 0; i < n; i++) { // 枚举第一只小猪
path[i][i] = 1 << i; // 注意:存在某些只覆盖一个点的抛物线,这个不容易想到,但打过愤怒的小鸟游戏的人,应该都知道一次未必就能打下两只以上的小猪
for (int j = i + 1; j < n; j++) { // j从i+1开始防止出现重复比如(1,3)和(3,1)其实是同一条抛物线
double x1 = pig[i].x, y1 = pig[i].y;
double x2 = pig[j].x, y2 = pig[j].y;
if (cmp(x1, x2) & cmp(y1, y2)) continue; // 如果存在重复输入的数据, 事实上出题人比较良心,没有给我们准备恶心的特判数据
// 但是为了保护下面的代码运算过程中肯定不出错做为一个程序员还是要加上上面这句cmp
double a = (y1 / x1 - y2 / x2) / (x1 - x2); // 推式子,见题解
double b = (y1 / x1) - a * x1; // 推式子,见题解
if (a >= 0) continue; // 抛物线开口需要向下
// 此抛物线可以覆盖掉哪些小猪
for (int k = 0; k < n; k++) {
double x = pig[k].x, y = pig[k].y;
if (cmp(a * x * x + b * x, y)) // 符合抛物线方程代表此抛物线可以覆盖掉k点
path[i][j] += 1 << k; // 记录此抛物线方程覆盖掉k号小猪
}
}
}
/*
2、DP 解决用最少多少条抛物线才能完全覆盖掉所有小猪
起点0000 终点:1111
办法:对于没有覆盖的小猪,引入经过它的抛物线,对比记录最小代价解
原来就是1的不用管了只考虑状态是0的就对了。
*/
memset(f, 0x3f, sizeof f); // 预求最小,先设最大,表示状态不合法或没有计算过
f[0] = 0; // 递推起点一只小猪也不覆盖掉需要0条抛物线这在现实中是合理的解释。
for (int x = 0; x < (1 << n) - 1; x++) { // 枚举每个存在0的状态, (1<<n)-1类似于 111111这样的形式因为现在的目标是找出含有数位是0的状态进行状态转移所以全是1的不用考虑
if (f[x] == INF) continue; // 如果当前状态是一个未推导状态,那么也不指望它能帮助其它人提供信息
for (int i = 0; i < n; i++) { // 枚举状态x的每一个数位也可以理解为每一只小猪看看它是不是还没有被覆盖到
if ((x >> i & 1) == 0) { // i号小猪现在还没有被覆盖到
for (int j = 0; j < n; j++) { // 枚举i号小猪参与的所有抛物线
if (path[i][j]) {
int k = path[i][j] | x; // 引入此抛物线(i,j)可以在x状态基础上覆盖掉更多的小猪到达k状态
f[k] = min(f[k], f[x] + 1); // 记录k状态是通过x状态在增加一条抛物线的代价下到达的
}
}
break;
/*
Q: 上面在枚举一个没有覆盖的点的时候为什么只需要枚举一个点就可以了?
A: 因为这里找出来的点i, 是为了进行下一步的状态转移,而不是为了把没有覆盖的点覆盖上。
因为我们枚举了所有的状态,每次找出一个未覆盖的,那最后也自然也能把所有状态都覆盖到,也就是所有状态都进行了转移尝试。
*/
}
}
}
// 递推的结果保存在所有状态都是1的结果数组中
printf("%d\n", f[(1 << n) - 1]);
}
return 0;
}