题目链接

题目大意:第一象限内有\(n\)个点(\(n \leq 18\)),求最少要多少条形如\(y=ax^2+bx \quad a<0,a,b, \in R\) 的抛物线才能覆盖所有点

状压\(dp\)


分析:\(n\)的数据范围很小,因此我们可以考虑状压\(dp\),填表法不好做我们可以用刷表法

\(f[S]\)表示覆盖集合\(S\)内点的最小代价

显然\(f[0]= 0\)

\(f[S|line[i][j]] = min\{f[S] + 1\}\)其中\(line[i][j]\)表示经过\(i,j\)两点的抛物线可以覆盖的点的集合

\(f[S | 1<<i] = min\{f[S] + 1\}\)

这样单次复杂度\(O(2^nn^2)\),有点悬

状态的\(2^n\)不能消掉我们从转移入手

原来每次枚举所有没有被覆盖的点是\(n^2\)的,我们固定其中一个点为没有被覆盖的点中编号最小的,这样转移就是\(n\)

为什么这样是对的?因为我们最终要求的是覆盖所有的点,当前状态不覆盖最小的点也会被下一个状态覆盖,所以可以直接钦定一个点

这题样例都卡精度,醉了

#include <algorithm>
#include <cmath>
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 23;
typedef long double type;
const type eps = 1e-7;
struct Pos{
	type x,y;
}val[maxn];
int t,n,m,dp[1 << maxn],lowbit[1 << maxn],line[maxn][maxn];//lowbit表示最小的为0的点的编号
inline bool cmp(type a,type b){return abs(a - b) < eps;}
void gauss(type &x,type &y,type a1,type b1,type c1,type a2,type b2,type c2){
	x = (c1 * b2 - c2 * b1) / (a1 * b2 - a2 * b1);
	y = (c1 - a1 * x) / b1;
}
inline void solve(){
	cin >> n >> m;
	for(int i = 0;i < n;i++)
		cin >> val[i].x >> val[i].y;
	memset(line,0,sizeof(line));
	memset(dp,0x3f,sizeof(dp));
	dp[0] = 0;
	for(int i = 0;i < n;i++)
		for(int j = 0;j < n;j++){
			if(cmp(val[i].x,val[j].x))continue;
			type a,b;
			gauss(a,b,val[i].x * val[i].x,val[i].x,val[i].y,val[j].x * val[j].x,val[j].x,val[j].y);
			if(a > -eps)continue;
			for(int k = 0;k < n;k++)
				if(cmp(a * val[k].x * val[k].x + b * val[k].x,val[k].y))line[i][j] |= 1 << k;
		}
	for(int i = 0;i < 1 << n;i++){
		int j = lowbit[i];
		dp[i | (1 << j)] = min(dp[i | (1 << j)],dp[i] + 1);
		for(int k = 0;k < n;k++)
			dp[i | line[j][k]] = min(dp[i | line[j][k]],dp[i] + 1);
	}
	cout << dp[(1 << n) - 1] << '\n';
}
int main(){
	for(int i = 0;i < (1 << 18);i++){
		int j = 0;
		for(;(i >> j) & 1;j++);
		lowbit[i] = j;
	}
	cin >> t;
	while(t--)solve();
	return 0;
}

相关文章: