运用状态压缩dp求解“重复覆盖问题”
本题是一个经典的“重复覆盖问题”,即给定01矩阵,要求选择尽量少的行,将所有列覆盖住
抛物线的问题
由于本题中抛物线严格经过原点,所以c=0
,那么只需要两点我们就可以确定一条抛物线y=ax2+bxy=ax2+bx
所以我们可以用两个小猪确定一个可以消灭它们的抛物线,考虑如何表示这个变量:
path[][]数组
path[i][j]
表示编号为i的小猪和编号为j
的小猪所在的抛物线,变量的属性为这个抛物线可消灭的小猪(编号)的二进制状态表示
例
: path[2][3] = 100111
表示由2
号猪和3
号猪确定的抛物线可以消灭编号1
,2
,3
,6
的小猪。下面考虑状态的表示和计算:
状态表示
f[i]
表示状态为i的所有集合中消灭小猪所需要的最少的小鸟个数(即抛物线个数)
状态计算
找到任意一个i
状态下没有被消灭的小猪的编号x
,枚举所有可消灭它的抛物线path[x][j]
并更新状态:
f[i | path[x][j]] = min(f[i | path[x][j]] , f[i] + 1);
状态转移的思路与暴搜的思路基本一致
由两个点计算抛物线的a 和 b
c++代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include <cmath>
using namespace std;
#define x first
#define y second
const int N = 18,M = 1 << 18;
const double eps = 1e-8; //凡是设计到计算double,运算的结果都要考虑精度
//1e-8表示10^-8次方
typedef pair<double,double> PDD;
PDD pos[N]; //保存每个小猪的坐标
int f[M]; //f[i]表示状态为i的所有集合中消灭小猪所需要的最少的小鸟个数
int path[N][N]; //表示点i和点j组成的抛物线对于所有点的覆盖情况(用二进制数表示)
int n,m;
//判断两个点是否相同
int cmp(double x, double y)
{
if (fabs(x - y) < eps) return 0;
if (x < y) return -1;
return 1;
}
int main(){
int t;
cin >> t;
while (t -- )
{
cin >> n >> m;
for (int i = 0; i < n; i ++ ) cin >> pos[i].x >> pos[i].y;
memset(path, 0, sizeof path);
//预处理:枚举所有点能组成的所有抛物线
for (int i = 0; i < n; i ++ )
{
path[i][i] = 1 << i; //特判:对于只覆盖一个点的抛物线
for (int j = 0; j < n; j ++ )
{
double x1 = pos[i].x, y1 = pos[i].y;
double x2 = pos[j].x, y2 = pos[j].y;
//如果两点的x坐标相同,那么斜率就是正无穷,不合法;但是如果两点y坐标相等,那么斜率为0,合法不用特判
if (!cmp(x1, x2)) continue;
double a = (y1 / x1 - y2 / x2) / (x1 - x2);
double b = y1 / x1 - a * x1;
if (cmp(a, 0) >= 0) continue; //抛物线开口是向下的
int state = 0; //表示当前抛物线能覆盖哪些点(用二进制数表示)
for (int k = 0; k < n; k ++ )
{
double x = pos[k].x, y = pos[k].y;
if (!cmp(a * x * x + b * x, y)) state += 1 << k;
}
path[i][j] = state;
}
}
memset(f, 0x3f, sizeof f);
f[0] = 0; //没有小猪的情况下不需要小鸟
for (int i = 0; i < 1 << n; i ++ ) //枚举小猪的所有可能的覆盖情况
{
int x = 0;
for (int j = 0; j < n; j ++ ) //枚举所有小猪,选择任意一个没有被当前状态覆盖的小猪
if (!(i >> j & 1))
{
x = j;
break;
}
//i | path[x][j]为新覆盖小猪x的新状态
for (int j = 0; j < n; j ++ )
f[i | path[x][j]] = min(f[i | path[x][j]], f[i] + 1);
}
//最后需要的是“i << n - 1状态”即“111111”的状态
cout << f[(1 << n) - 1] << endl;
}
return 0;
}
if (!cmp(x1, x2)) continue; //如果两个点相同就表示之间没有抛物线???
这句话怎么理解?? 如果x1和x2 相同为什么就是两个点相等??为什么就是不能有抛物线??
理解有误,应该是斜率不合法的问题
已更正