你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

【刷题】动态规划——状态压缩DP(集合类):愤怒的小鸟(NOIP2016)【重复覆盖问题】

2021/12/31 4:23:07

在这里插入图片描述

题目链接

题目求的是:给定一些点,求最少用几条抛物线覆盖所有的点。

由于N很小,且抛物线过原点,因此只需要2个点就能确定一条抛物线。可以预先处理出所有可能的抛物线和经过的点,因此这题就变成重复覆盖问题。可以考虑用DLX

这边简单起见,用状压DP的方法,每个点被覆盖或没被覆盖看作一个状态1或0。
例如s = 1011,代表第1个点、第2个点、第4个点被覆盖了
f[s] = f[s'] + 1,对于每个s',枚举其中没覆盖的点x,并选择覆盖x的抛物线进行覆盖,设抛物线覆盖的点的状态是p[x, j],则s = s' | p[x, j]

计算抛物线如下:
ax12 + bx1 = y1 --> ax1 + b = y1 / x1
ax22 + bx2 = y2 --> ax2 + b = y2 / x2
俩式相减
a(x1 - x2) = y1 / x1 - y2 / x2

综上
a = (y1 / x1 - y2 / x2) / (x1 - x2)
b = y1 / x1 - ax1

注意a < 0

#include <iostream>
#include <cmath>
#include <cstring>

#define x first
#define y second

using namespace std;

const double eps = 1e-8;

int t, n, m, lp;
int f[1 << 18], par[20][20];
typedef pair<double, double> coo;
coo pig[20];
int main() {
	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);
		}
		lp = 0;
		memset(par, 0, sizeof(par));
		for (int i = 0; i < n; i ++ ) {
			par[i][i] = 1 << i;
			for (int j = 0; j < n; j ++ ) {
				if (i != j && pig[i].x - pig[j].x > eps) {
					double a = (pig[i].y / pig[i].x - pig[j].y / pig[j].x) / (pig[i].x - pig[j].x);
					if (a >= 0) continue;
					
					double b = pig[i].y / pig[i].x - a * pig[i].x;
					int state = 0;
					for (int k = 0; k < n; k ++ ) {
						if (abs(a * pig[k].x * pig[k].x + b * pig[k].x - pig[k].y) < eps) {
							state += 1 << k;
						}
					}
					par[i][j] = par[j][i] = 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;
				}
			}
			for (int j = 0; j < n; j ++ ) {
				f[i | par[x][j]] = min(f[i | par[x][j]], f[i] + 1);
			}
		}
		printf("%d\n", f[(1 << n) - 1]);
	}
	
	
	return 0;
}